Add multipart machines system (#35969)
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Content.Client.Machines.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Component attached to all multipart machine ghosts
|
||||||
|
/// Intended for client side usage only, but used on prototypes.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class MultipartMachineGhostComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Machine this particular ghost is linked to.
|
||||||
|
/// </summary>
|
||||||
|
public EntityUid? LinkedMachine = null;
|
||||||
|
}
|
||||||
109
Content.Client/Machines/EntitySystems/MultipartMachineSystem.cs
Normal file
109
Content.Client/Machines/EntitySystems/MultipartMachineSystem.cs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
using Content.Client.Examine;
|
||||||
|
using Content.Client.Machines.Components;
|
||||||
|
using Content.Shared.Machines.Components;
|
||||||
|
using Content.Shared.Machines.EntitySystems;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.Manager;
|
||||||
|
using Robust.Shared.Spawners;
|
||||||
|
|
||||||
|
namespace Content.Client.Machines.EntitySystems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Client side handling of multipart machines.
|
||||||
|
/// Handles client side examination events to show the expected layout of the machine
|
||||||
|
/// based on the origin of the main entity.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MultipartMachineSystem : SharedMultipartMachineSystem
|
||||||
|
{
|
||||||
|
private readonly EntProtoId _ghostPrototype = "MultipartMachineGhost";
|
||||||
|
private readonly Color _partiallyTransparent = new Color(255, 255, 255, 180);
|
||||||
|
|
||||||
|
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
|
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||||
|
[Dependency] private readonly ISerializationManager _serialization= default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<MultipartMachineComponent, ClientExaminedEvent>(OnMachineExamined);
|
||||||
|
SubscribeLocalEvent<MultipartMachineComponent, AfterAutoHandleStateEvent>(OnHandleState);
|
||||||
|
SubscribeLocalEvent<MultipartMachineGhostComponent, TimedDespawnEvent>(OnGhostDespawned);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles spawning several ghost sprites to show where the different parts of the machine
|
||||||
|
/// should go and the rotations they're expected to have.
|
||||||
|
/// Can only show one set of ghost parts at a time and their location depends on the current map/grid
|
||||||
|
/// location of the origin machine.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Entity/Component that has been inspected.</param>
|
||||||
|
/// <param name="args">Args for the event.</param>
|
||||||
|
private void OnMachineExamined(Entity<MultipartMachineComponent> ent, ref ClientExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (ent.Comp.Ghosts.Count != 0)
|
||||||
|
{
|
||||||
|
// Already showing some part ghosts
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var part in ent.Comp.Parts.Values)
|
||||||
|
{
|
||||||
|
if (part.Entity.HasValue)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var entityCoords = new EntityCoordinates(ent.Owner, part.Offset);
|
||||||
|
var ghostEnt = Spawn(_ghostPrototype, entityCoords);
|
||||||
|
|
||||||
|
if (!XformQuery.TryGetComponent(ghostEnt, out var xform))
|
||||||
|
break;
|
||||||
|
|
||||||
|
xform.LocalRotation = part.Rotation;
|
||||||
|
|
||||||
|
Comp<MultipartMachineGhostComponent>(ghostEnt).LinkedMachine = ent;
|
||||||
|
|
||||||
|
ent.Comp.Ghosts.Add(ghostEnt);
|
||||||
|
|
||||||
|
if (part.GhostProto == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var entProto = _prototype.Index(part.GhostProto.Value);
|
||||||
|
if (!entProto.Components.TryGetComponent("Sprite", out var s) || s is not SpriteComponent protoSprite)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ghostSprite = EnsureComp<SpriteComponent>(ghostEnt);
|
||||||
|
_serialization.CopyTo(protoSprite, ref ghostSprite, notNullableOverride: true);
|
||||||
|
|
||||||
|
_sprite.SetColor((ghostEnt, ghostSprite), _partiallyTransparent);
|
||||||
|
|
||||||
|
_metaData.SetEntityName(ghostEnt, entProto.Name);
|
||||||
|
_metaData.SetEntityDescription(ghostEnt, entProto.Description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHandleState(Entity<MultipartMachineComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||||
|
{
|
||||||
|
foreach (var part in ent.Comp.Parts.Values)
|
||||||
|
{
|
||||||
|
part.Entity = part.NetEntity.HasValue ? EnsureEntity<MultipartMachinePartComponent>(part.NetEntity.Value, ent) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles when a ghost part despawns after its short lifetime.
|
||||||
|
/// Will attempt to remove itself from the list of known ghost entities in the main multipart
|
||||||
|
/// machine component.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Ghost entity that has been despawned.</param>
|
||||||
|
/// <param name="args">Args for the event.</param>
|
||||||
|
private void OnGhostDespawned(Entity<MultipartMachineGhostComponent> ent, ref TimedDespawnEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<MultipartMachineComponent>(ent.Comp.LinkedMachine, out var machine))
|
||||||
|
return;
|
||||||
|
|
||||||
|
machine.Ghosts.Remove(ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
<Control/>
|
<Control/>
|
||||||
<ui:PASegmentControl Name="EndCapTexture" BaseState="end_cap"/>
|
<ui:PASegmentControl Name="EndCapTexture" BaseState="end_cap"/>
|
||||||
<Control/>
|
<Control/>
|
||||||
<ui:PASegmentControl Name="ControlBoxTexture" BaseState="control_box"/>
|
<ui:PASegmentControl Name="ControlBoxTexture" BaseState="control_box" DefaultVisible="True"/>
|
||||||
<ui:PASegmentControl Name="FuelChamberTexture" BaseState="fuel_chamber"/>
|
<ui:PASegmentControl Name="FuelChamberTexture" BaseState="fuel_chamber"/>
|
||||||
<Control/>
|
<Control/>
|
||||||
<Control/>
|
<Control/>
|
||||||
|
|||||||
@@ -268,6 +268,7 @@ public sealed class PASegmentControl : Control
|
|||||||
private RSI? _rsi;
|
private RSI? _rsi;
|
||||||
|
|
||||||
public string BaseState { get; set; } = "control_box";
|
public string BaseState { get; set; } = "control_box";
|
||||||
|
public bool DefaultVisible { get; set; } = false;
|
||||||
|
|
||||||
public PASegmentControl()
|
public PASegmentControl()
|
||||||
{
|
{
|
||||||
@@ -283,12 +284,14 @@ public sealed class PASegmentControl : Control
|
|||||||
_rsi = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>($"/Textures/Structures/Power/Generation/PA/{BaseState}.rsi").RSI;
|
_rsi = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>($"/Textures/Structures/Power/Generation/PA/{BaseState}.rsi").RSI;
|
||||||
MinSize = _rsi.Size;
|
MinSize = _rsi.Size;
|
||||||
_base.Texture = _rsi["completed"].Frame0;
|
_base.Texture = _rsi["completed"].Frame0;
|
||||||
|
|
||||||
|
SetVisible(DefaultVisible);
|
||||||
|
_unlit.Visible = DefaultVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPowerState(ParticleAcceleratorUIState state, bool exists)
|
public void SetPowerState(ParticleAcceleratorUIState state, bool exists)
|
||||||
{
|
{
|
||||||
_base.ShaderOverride = exists ? null : _greyScaleShader;
|
SetVisible(exists);
|
||||||
_base.ModulateSelfOverride = exists ? null : new Color(127, 127, 127);
|
|
||||||
|
|
||||||
if (!state.Enabled || !exists)
|
if (!state.Enabled || !exists)
|
||||||
{
|
{
|
||||||
@@ -319,4 +322,23 @@ public sealed class PASegmentControl : Control
|
|||||||
|
|
||||||
_unlit.Texture = rState.Frame0;
|
_unlit.Texture = rState.Frame0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds/Removes the shading to the part in the control menu based on the
|
||||||
|
/// input state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">True if the part exists, false otherwise</param>
|
||||||
|
private void SetVisible(bool state)
|
||||||
|
{
|
||||||
|
if (state)
|
||||||
|
{
|
||||||
|
_base.ShaderOverride = null;
|
||||||
|
_base.ModulateSelfOverride = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_base.ShaderOverride = _greyScaleShader;
|
||||||
|
_base.ModulateSelfOverride = new Color(127, 127, 127);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ namespace Content.Server.Construction
|
|||||||
|
|
||||||
// ChangeEntity will handle the pathfinding update.
|
// ChangeEntity will handle the pathfinding update.
|
||||||
if (node.Entity.GetId(uid, userUid, new(EntityManager)) is { } newEntity
|
if (node.Entity.GetId(uid, userUid, new(EntityManager)) is { } newEntity
|
||||||
&& ChangeEntity(uid, userUid, newEntity, construction) != null)
|
&& ChangeEntity(uid, userUid, newEntity, construction, oldNode) != null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (performActions)
|
if (performActions)
|
||||||
@@ -281,6 +281,7 @@ namespace Content.Server.Construction
|
|||||||
/// <param name="userUid">An optional user entity, for actions.</param>
|
/// <param name="userUid">An optional user entity, for actions.</param>
|
||||||
/// <param name="newEntity">The entity prototype identifier for the new entity.</param>
|
/// <param name="newEntity">The entity prototype identifier for the new entity.</param>
|
||||||
/// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
|
/// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
|
||||||
|
/// <param name="previousNode">The previous node, if any, this graph was on before changing entity.</param>
|
||||||
/// <param name="metaData">The metadata component of the target entity. Will be resolved if null.</param>
|
/// <param name="metaData">The metadata component of the target entity. Will be resolved if null.</param>
|
||||||
/// <param name="transform">The transform component of the target entity. Will be resolved if null.</param>
|
/// <param name="transform">The transform component of the target entity. Will be resolved if null.</param>
|
||||||
/// <param name="containerManager">The container manager component of the target entity. Will be resolved if null,
|
/// <param name="containerManager">The container manager component of the target entity. Will be resolved if null,
|
||||||
@@ -288,6 +289,7 @@ namespace Content.Server.Construction
|
|||||||
/// <returns>The new entity, or null if the method did not succeed.</returns>
|
/// <returns>The new entity, or null if the method did not succeed.</returns>
|
||||||
private EntityUid? ChangeEntity(EntityUid uid, EntityUid? userUid, string newEntity,
|
private EntityUid? ChangeEntity(EntityUid uid, EntityUid? userUid, string newEntity,
|
||||||
ConstructionComponent? construction = null,
|
ConstructionComponent? construction = null,
|
||||||
|
string? previousNode = null,
|
||||||
MetaDataComponent? metaData = null,
|
MetaDataComponent? metaData = null,
|
||||||
TransformComponent? transform = null,
|
TransformComponent? transform = null,
|
||||||
ContainerManagerComponent? containerManager = null)
|
ContainerManagerComponent? containerManager = null)
|
||||||
@@ -407,6 +409,11 @@ namespace Content.Server.Construction
|
|||||||
|
|
||||||
QueueDel(uid);
|
QueueDel(uid);
|
||||||
|
|
||||||
|
// If ChangeEntity has ran, then the entity uid has changed and the
|
||||||
|
// new entity should be initialized by this point.
|
||||||
|
var afterChangeEv = new AfterConstructionChangeEntityEvent(construction.Graph, construction.Node, previousNode);
|
||||||
|
RaiseLocalEvent(newUid, ref afterChangeEv);
|
||||||
|
|
||||||
return newUid;
|
return newUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,4 +460,16 @@ namespace Content.Server.Construction
|
|||||||
Old = oldUid;
|
Old = oldUid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This event is raised after an entity changes prototype/uid during construction.
|
||||||
|
/// This is only raised at the new entity, after it has been initialized.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Graph">Construction graph for this entity.</param>
|
||||||
|
/// <param name="CurrentNode">New node that has become active.</param>
|
||||||
|
/// <param name="PreviousNode">Previous node that was active on the graph.</param>
|
||||||
|
[ByRefEvent]
|
||||||
|
public record struct AfterConstructionChangeEntityEvent(string Graph, string CurrentNode, string? PreviousNode)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ namespace Content.Server.Entry
|
|||||||
"InventorySlots",
|
"InventorySlots",
|
||||||
"LightFade",
|
"LightFade",
|
||||||
"HolidayRsiSwap",
|
"HolidayRsiSwap",
|
||||||
"OptionsVisualizer"
|
"OptionsVisualizer",
|
||||||
|
"MultipartMachineGhost"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
352
Content.Server/Machines/EntitySystems/MultipartMachineSystem.cs
Normal file
352
Content.Server/Machines/EntitySystems/MultipartMachineSystem.cs
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
using Content.Server.Construction;
|
||||||
|
using Content.Server.Construction.Components;
|
||||||
|
using Content.Shared.Machines.Components;
|
||||||
|
using Content.Shared.Machines.EntitySystems;
|
||||||
|
using Content.Shared.Machines.Events;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.Machines.EntitySystems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server side handling of multipart machines.
|
||||||
|
/// When requested, performs scans of the map area around the specified entity
|
||||||
|
/// to find and match parts of the machine.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MultipartMachineSystem : SharedMultipartMachineSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||||
|
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||||
|
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
|
||||||
|
|
||||||
|
// The largest size ANY machine can theoretically have.
|
||||||
|
// Used to aid search for machines in range of parts that have been anchored/constructed.
|
||||||
|
private const float MaximumRange = 30;
|
||||||
|
private readonly HashSet<Entity<MultipartMachineComponent>> _entitiesInRange = [];
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<MultipartMachineComponent, ComponentStartup>(OnComponentStartup);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<MultipartMachineComponent, AnchorStateChangedEvent>(OnMachineAnchorChanged);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<MultipartMachinePartComponent, AfterConstructionChangeEntityEvent>(OnPartConstructionNodeChanged);
|
||||||
|
SubscribeLocalEvent<MultipartMachinePartComponent, AnchorStateChangedEvent>(OnPartAnchorChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the matched entity from the specified part
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Entity to clear the part for.</param>
|
||||||
|
/// <param name="part">Enum value for the part to clear.</param>
|
||||||
|
public void ClearPartEntity(Entity<MultipartMachineComponent?> ent, Enum part)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!ent.Comp.Parts.TryGetValue(part, out var value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!value.Entity.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var partEnt = value.Entity.Value;
|
||||||
|
var partComp = EnsureComp<MultipartMachinePartComponent>(partEnt);
|
||||||
|
|
||||||
|
if (partComp.Master.HasValue)
|
||||||
|
{
|
||||||
|
partComp.Master = null;
|
||||||
|
Dirty(partEnt, partComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
value.Entity = null;
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a rescan of all parts of the machine to confirm they exist and match
|
||||||
|
/// the specified requirements for offset, rotation, and components.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Entity to rescan for.</param>
|
||||||
|
/// <param name="user">Optional user entity which has caused this rescan.</param>
|
||||||
|
/// <returns>True the state of the machine's assembly has changed, false otherwise.</returns>
|
||||||
|
public bool Rescan(Entity<MultipartMachineComponent> ent, EntityUid? user = null)
|
||||||
|
{
|
||||||
|
// Get all required transform information to start looking for the other parts based on their offset
|
||||||
|
if (!XformQuery.TryGetComponent(ent.Owner, out var xform) || !xform.Anchored)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var gridUid = xform.GridUid;
|
||||||
|
if (gridUid == null || gridUid != xform.ParentUid || !TryComp<MapGridComponent>(gridUid, out var grid))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Whichever component has the MultipartMachine component should be treated as the origin
|
||||||
|
var machineOrigin = _mapSystem.TileIndicesFor(gridUid.Value, grid, xform.Coordinates);
|
||||||
|
|
||||||
|
// Set to true if any of the parts' state changes
|
||||||
|
var stateHasChanged = false;
|
||||||
|
|
||||||
|
// Keep a track of what parts were added or removed so we can inform listeners
|
||||||
|
Dictionary<Enum, EntityUid> partsAdded = [];
|
||||||
|
Dictionary<Enum, EntityUid> partsRemoved = [];
|
||||||
|
|
||||||
|
var missingParts = false;
|
||||||
|
var machineRotation = xform.LocalRotation.GetCardinalDir().ToAngle();
|
||||||
|
foreach (var (key, part) in ent.Comp.Parts)
|
||||||
|
{
|
||||||
|
var originalPart = part.Entity;
|
||||||
|
part.Entity = null;
|
||||||
|
|
||||||
|
if (!_factory.TryGetRegistration(part.Component, out var registration))
|
||||||
|
break;
|
||||||
|
|
||||||
|
var query = EntityManager.GetEntityQuery(registration.Type);
|
||||||
|
|
||||||
|
ScanPart(machineOrigin, machineRotation, query, gridUid.Value, grid, part);
|
||||||
|
|
||||||
|
if (!part.Entity.HasValue && !part.Optional)
|
||||||
|
missingParts = true;
|
||||||
|
|
||||||
|
if (part.Entity == originalPart)
|
||||||
|
continue; // Nothing has changed here
|
||||||
|
|
||||||
|
stateHasChanged = true;
|
||||||
|
|
||||||
|
MultipartMachinePartComponent comp;
|
||||||
|
EntityUid partEnt;
|
||||||
|
if (part.Entity.HasValue)
|
||||||
|
{
|
||||||
|
// This part gained an entity, add the Part component so it can find out which machine
|
||||||
|
// it's a part of
|
||||||
|
partEnt = part.Entity.Value;
|
||||||
|
comp = EnsureComp<MultipartMachinePartComponent>(partEnt);
|
||||||
|
comp.Master = ent;
|
||||||
|
partsAdded.Add(key, partEnt);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This part lost its entity, ensure we clean up the old entity so it's no longer marked
|
||||||
|
// as something we care about.
|
||||||
|
partEnt = originalPart!.Value;
|
||||||
|
comp = EnsureComp<MultipartMachinePartComponent>(partEnt);
|
||||||
|
comp.Master = null;
|
||||||
|
partsRemoved.Add(key, partEnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dirty(partEnt, comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
ent.Comp.IsAssembled = !missingParts;
|
||||||
|
if (stateHasChanged)
|
||||||
|
{
|
||||||
|
var ev = new MultipartMachineAssemblyStateChanged(
|
||||||
|
ent,
|
||||||
|
ent.Comp.IsAssembled,
|
||||||
|
user,
|
||||||
|
partsAdded,
|
||||||
|
partsRemoved
|
||||||
|
);
|
||||||
|
RaiseLocalEvent(ent, ref ev);
|
||||||
|
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stateHasChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears all entities bound to parts for a specified machine.
|
||||||
|
/// Will also raise the assembly state change and dirty event for it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Machine to completely clear the parts of.</param>
|
||||||
|
private void ClearAllParts(Entity<MultipartMachineComponent> ent)
|
||||||
|
{
|
||||||
|
var stateHasChanged = false;
|
||||||
|
|
||||||
|
Dictionary<Enum, EntityUid> clearedParts = [];
|
||||||
|
foreach (var (key, part) in ent.Comp.Parts)
|
||||||
|
{
|
||||||
|
if (!part.Entity.HasValue)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
stateHasChanged = true;
|
||||||
|
var partEntity = part.Entity.Value;
|
||||||
|
var partComp = EnsureComp<MultipartMachinePartComponent>(partEntity);
|
||||||
|
|
||||||
|
clearedParts.Add(key, partEntity);
|
||||||
|
|
||||||
|
part.Entity = null;
|
||||||
|
part.NetEntity = null;
|
||||||
|
|
||||||
|
partComp.Master = null;
|
||||||
|
Dirty(partEntity, partComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
ent.Comp.IsAssembled = false;
|
||||||
|
|
||||||
|
if (stateHasChanged)
|
||||||
|
{
|
||||||
|
var ev = new MultipartMachineAssemblyStateChanged(
|
||||||
|
ent,
|
||||||
|
ent.Comp.IsAssembled,
|
||||||
|
null,
|
||||||
|
[],
|
||||||
|
clearedParts
|
||||||
|
);
|
||||||
|
RaiseLocalEvent(ent, ref ev);
|
||||||
|
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles any additional setup of the MultipartMachine component.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Entity/Component that just started.</param>
|
||||||
|
/// <param name="args">Args for the startup.</param>
|
||||||
|
private void OnComponentStartup(Entity<MultipartMachineComponent> ent, ref ComponentStartup args)
|
||||||
|
{
|
||||||
|
// If anchored, perform a rescan of this machine when the component starts so we can immediately
|
||||||
|
// jump to an assembled state if needed.
|
||||||
|
if (XformQuery.TryGetComponent(ent.Owner, out var xform) && xform.Anchored)
|
||||||
|
Rescan(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles when a main machine entity has been anchored or unanchored by a user.
|
||||||
|
/// Rescanning is then required in order to check whether parts are still in the right places,
|
||||||
|
/// and raise a AfterConstructionChangeEntityEvent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Machine entity that has been anchored or unanchored.</param>
|
||||||
|
/// <param name="args">Args for this event.</param>
|
||||||
|
private void OnMachineAnchorChanged(Entity<MultipartMachineComponent> ent,
|
||||||
|
ref AnchorStateChangedEvent args)
|
||||||
|
{
|
||||||
|
if (args.Anchored)
|
||||||
|
Rescan(ent);
|
||||||
|
else
|
||||||
|
ClearAllParts(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles when a machine part entity has been created due to a move in a construction graph.
|
||||||
|
/// Rescans all known multipart machines within range that have a part which matches that specific graph
|
||||||
|
/// and node IDs.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Machine part entity that has moved in a graph.</param>
|
||||||
|
/// <param name="args">Args for this event.</param>
|
||||||
|
private void OnPartConstructionNodeChanged(Entity<MultipartMachinePartComponent> ent,
|
||||||
|
ref AfterConstructionChangeEntityEvent args)
|
||||||
|
{
|
||||||
|
if (!XformQuery.TryGetComponent(ent.Owner, out var constructXform))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lookupSystem.GetEntitiesInRange(constructXform.Coordinates, MaximumRange, _entitiesInRange);
|
||||||
|
foreach (var machine in _entitiesInRange)
|
||||||
|
{
|
||||||
|
foreach (var part in machine.Comp.Parts.Values)
|
||||||
|
{
|
||||||
|
if (args.Graph == part.Graph &&
|
||||||
|
(args.PreviousNode == part.ExpectedNode || args.CurrentNode == part.ExpectedNode))
|
||||||
|
{
|
||||||
|
Rescan(machine);
|
||||||
|
break; // No need to scan the same machine again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles when a machine part entity has been anchored or unanchored by a user.
|
||||||
|
/// We might be able to link an unanchored part to a machine, but anchoring a constructable entity,
|
||||||
|
/// which machine parts are, will require a rescan of all machines within range as we have no idea
|
||||||
|
/// what machine it might be a part of.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Machine part entity that has been anchored or unanchored.</param>
|
||||||
|
/// <param name="args">Args for this event, notably the anchor status.</param>
|
||||||
|
private void OnPartAnchorChanged(Entity<MultipartMachinePartComponent> ent, ref AnchorStateChangedEvent args)
|
||||||
|
{
|
||||||
|
if (!args.Anchored)
|
||||||
|
{
|
||||||
|
if (!TryComp<MultipartMachinePartComponent>(ent.Owner, out var part) || !part.Master.HasValue)
|
||||||
|
return; // This is not an entity we care about
|
||||||
|
|
||||||
|
// This is a machine part that is being unanchored, rescan its machine
|
||||||
|
if (!TryComp<MultipartMachineComponent>(part.Master, out var machine))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Rescan((part.Master.Value, machine));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're anchoring some construction, we have no idea which machine this might be for
|
||||||
|
// so we have to just check everyone in range and perform a rescan.
|
||||||
|
if (!XformQuery.TryGetComponent(ent.Owner, out var constructXform))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lookupSystem.GetEntitiesInRange(constructXform.Coordinates, MaximumRange, _entitiesInRange);
|
||||||
|
foreach (var machine in _entitiesInRange)
|
||||||
|
{
|
||||||
|
if (Rescan(machine) && HasPartEntity(machine.AsNullable(), ent.Owner))
|
||||||
|
return; // This machine is using this entity so we don't need to go any further
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scans the specified coordinates for any anchored entities that might match the given
|
||||||
|
/// component and rotation requirements.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="machineOrigin">Origin coordinates for the machine.</param>
|
||||||
|
/// <param name="rotation">Rotation of the master entity to use when searching for this part.</param>
|
||||||
|
/// <param name="query">Entity query for the specific component the entity must have.</param>
|
||||||
|
/// <param name="gridUid">EntityUID of the grid to use for the lookup.</param>
|
||||||
|
/// <param name="grid">Grid to use for the lookup.</param>
|
||||||
|
/// <param name="part">Part we're searching for.</param>
|
||||||
|
/// <returns>True when part is found and matches requirements, false otherwise.</returns>
|
||||||
|
private bool ScanPart(
|
||||||
|
Vector2i machineOrigin,
|
||||||
|
Angle rotation,
|
||||||
|
EntityQuery<IComponent> query,
|
||||||
|
EntityUid gridUid,
|
||||||
|
MapGridComponent grid,
|
||||||
|
MachinePart part)
|
||||||
|
{
|
||||||
|
// Safety first, nuke any existing data
|
||||||
|
part.Entity = null;
|
||||||
|
|
||||||
|
var expectedLocation = machineOrigin + part.Offset.Rotate(rotation);
|
||||||
|
var expectedRotation = part.Rotation + rotation;
|
||||||
|
|
||||||
|
foreach (var entity in _mapSystem.GetAnchoredEntities(gridUid, grid, expectedLocation))
|
||||||
|
{
|
||||||
|
if (TerminatingOrDeleted(entity))
|
||||||
|
{
|
||||||
|
// Ignore entities which are in the process of being deleted
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query.TryGetComponent(entity, out var comp) ||
|
||||||
|
!Transform(entity).LocalRotation.EqualsApprox(expectedRotation.Theta))
|
||||||
|
{
|
||||||
|
// Either has no transform, or doesn't match the rotation
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryComp<ConstructionComponent>(entity, out var construction) ||
|
||||||
|
construction.Graph != part.Graph ||
|
||||||
|
construction.Node != part.ExpectedNode)
|
||||||
|
{
|
||||||
|
// This constructable doesn't match the right graph we expect
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
part.Entity = entity;
|
||||||
|
part.NetEntity = GetNetEntity(entity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,12 +13,6 @@ namespace Content.Server.ParticleAccelerator.Components;
|
|||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class ParticleAcceleratorControlBoxComponent : Component
|
public sealed partial class ParticleAcceleratorControlBoxComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Whether the PA parts have been correctly arranged to make a functional device.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
public bool Assembled = false;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the PA is currently set to fire at the console.
|
/// Whether the PA is currently set to fire at the console.
|
||||||
/// Requires <see cref="Assembled"/> to be true.
|
/// Requires <see cref="Assembled"/> to be true.
|
||||||
@@ -40,12 +34,6 @@ public sealed partial class ParticleAcceleratorControlBoxComponent : Component
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public bool Firing = false;
|
public bool Firing = false;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Block re-entrant rescanning.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public bool CurrentlyRescanning = false;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the PA is currently firing or charging to fire.
|
/// Whether the PA is currently firing or charging to fire.
|
||||||
/// Bounded by <see cref="ParticleAcceleratorPowerState.Standby"/> and <see cref="MaxStrength"/>.
|
/// Bounded by <see cref="ParticleAcceleratorPowerState.Standby"/> and <see cref="MaxStrength"/>.
|
||||||
@@ -61,48 +49,6 @@ public sealed partial class ParticleAcceleratorControlBoxComponent : Component
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public ParticleAcceleratorPowerState MaxStrength = ParticleAcceleratorPowerState.Level2;
|
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>
|
/// <summary>
|
||||||
/// The amount of power the particle accelerator must be provided with relative to the expected power draw to function.
|
/// The amount of power the particle accelerator must be provided with relative to the expected power draw to function.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Content.Server.ParticleAccelerator.Components;
|
|
||||||
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed partial class ParticleAcceleratorPartComponent : Component
|
|
||||||
{
|
|
||||||
[ViewVariables]
|
|
||||||
public EntityUid? Master;
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Content.Server.ParticleAccelerator.Components;
|
using Content.Server.ParticleAccelerator.Components;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.Machines.Components;
|
||||||
using Content.Shared.Singularity.Components;
|
using Content.Shared.Singularity.Components;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -10,6 +11,8 @@ using Content.Shared.Power;
|
|||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Content.Shared.ParticleAccelerator;
|
||||||
|
using Content.Shared.Machines.Events;
|
||||||
|
|
||||||
namespace Content.Server.ParticleAccelerator.EntitySystems;
|
namespace Content.Server.ParticleAccelerator.EntitySystems;
|
||||||
|
|
||||||
@@ -20,12 +23,11 @@ public sealed partial class ParticleAcceleratorSystem
|
|||||||
|
|
||||||
private void InitializeControlBoxSystem()
|
private void InitializeControlBoxSystem()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ComponentStartup>(OnComponentStartup);
|
|
||||||
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ComponentShutdown>(OnComponentShutdown);
|
|
||||||
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, PowerChangedEvent>(OnControlBoxPowerChange);
|
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, PowerChangedEvent>(OnControlBoxPowerChange);
|
||||||
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ParticleAcceleratorSetEnableMessage>(OnUISetEnableMessage);
|
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ParticleAcceleratorSetEnableMessage>(OnUISetEnableMessage);
|
||||||
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ParticleAcceleratorSetPowerStateMessage>(OnUISetPowerMessage);
|
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ParticleAcceleratorSetPowerStateMessage>(OnUISetPowerMessage);
|
||||||
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ParticleAcceleratorRescanPartsMessage>(OnUIRescanMessage);
|
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ParticleAcceleratorRescanPartsMessage>(OnUIRescanMessage);
|
||||||
|
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, MultipartMachineAssemblyStateChanged>(OnMachineAssembledChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
@@ -40,14 +42,12 @@ public sealed partial class ParticleAcceleratorSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Conditional("DEBUG")]
|
[Conditional("DEBUG")]
|
||||||
private void EverythingIsWellToFire(ParticleAcceleratorControlBoxComponent controller)
|
private void EverythingIsWellToFire(ParticleAcceleratorControlBoxComponent controller,
|
||||||
|
Entity<MultipartMachineComponent> machine)
|
||||||
{
|
{
|
||||||
DebugTools.Assert(controller.Powered);
|
DebugTools.Assert(controller.Powered);
|
||||||
DebugTools.Assert(controller.SelectedStrength != ParticleAcceleratorPowerState.Standby);
|
DebugTools.Assert(controller.SelectedStrength != ParticleAcceleratorPowerState.Standby);
|
||||||
DebugTools.Assert(controller.Assembled);
|
DebugTools.Assert(machine.Comp.IsAssembled);
|
||||||
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)
|
public void Fire(EntityUid uid, TimeSpan curTime, ParticleAcceleratorControlBoxComponent? comp = null)
|
||||||
@@ -58,12 +58,17 @@ public sealed partial class ParticleAcceleratorSystem
|
|||||||
comp.LastFire = curTime;
|
comp.LastFire = curTime;
|
||||||
comp.NextFire = curTime + comp.ChargeTime;
|
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;
|
var strength = comp.SelectedStrength;
|
||||||
FireEmitter(comp.PortEmitter!.Value, strength);
|
|
||||||
FireEmitter(comp.ForeEmitter!.Value, strength);
|
FireEmitter(_multipartMachine.GetPartEntity(machine, AcceleratorParts.PortEmitter)!.Value, strength);
|
||||||
FireEmitter(comp.StarboardEmitter!.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)
|
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))
|
if (!Resolve(uid, ref comp))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DebugTools.Assert(comp.Assembled);
|
DebugTools.Assert(_multipartMachine.IsAssembled((uid, null)));
|
||||||
|
|
||||||
if (comp.Enabled || !comp.CanBeEnabled)
|
if (comp.Enabled || !comp.CanBeEnabled)
|
||||||
return;
|
return;
|
||||||
@@ -82,9 +87,11 @@ public sealed partial class ParticleAcceleratorSystem
|
|||||||
comp.Enabled = true;
|
comp.Enabled = true;
|
||||||
UpdatePowerDraw(uid, comp);
|
UpdatePowerDraw(uid, comp);
|
||||||
|
|
||||||
if (!TryComp<PowerConsumerComponent>(comp.PowerBox, out var powerConsumer)
|
if (!TryComp<PowerConsumerComponent>(_multipartMachine.GetPartEntity(uid, AcceleratorParts.PowerBox), out var powerConsumer)
|
||||||
|| powerConsumer.ReceivedPower >= powerConsumer.DrawRate * ParticleAcceleratorControlBoxComponent.RequiredPowerRatio)
|
|| powerConsumer.ReceivedPower >= powerConsumer.DrawRate * ParticleAcceleratorControlBoxComponent.RequiredPowerRatio)
|
||||||
|
{
|
||||||
PowerOn(uid, comp);
|
PowerOn(uid, comp);
|
||||||
|
}
|
||||||
|
|
||||||
UpdateUI(uid, comp);
|
UpdateUI(uid, comp);
|
||||||
}
|
}
|
||||||
@@ -112,7 +119,7 @@ public sealed partial class ParticleAcceleratorSystem
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
DebugTools.Assert(comp.Enabled);
|
DebugTools.Assert(comp.Enabled);
|
||||||
DebugTools.Assert(comp.Assembled);
|
DebugTools.Assert(_multipartMachine.IsAssembled((uid, null)));
|
||||||
|
|
||||||
if (comp.Powered)
|
if (comp.Powered)
|
||||||
return;
|
return;
|
||||||
@@ -211,7 +218,10 @@ public sealed partial class ParticleAcceleratorSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EverythingIsWellToFire(comp);
|
if (!TryComp<MultipartMachineComponent>(uid, out var machine))
|
||||||
|
return;
|
||||||
|
|
||||||
|
EverythingIsWellToFire(comp, (uid, machine));
|
||||||
|
|
||||||
var curTime = _gameTiming.CurTime;
|
var curTime = _gameTiming.CurTime;
|
||||||
comp.LastFire = curTime;
|
comp.LastFire = curTime;
|
||||||
@@ -223,7 +233,8 @@ public sealed partial class ParticleAcceleratorSystem
|
|||||||
{
|
{
|
||||||
if (!Resolve(uid, ref comp))
|
if (!Resolve(uid, ref comp))
|
||||||
return;
|
return;
|
||||||
if (!TryComp<PowerConsumerComponent>(comp.PowerBox, out var powerConsumer))
|
|
||||||
|
if (!TryComp<PowerConsumerComponent>(_multipartMachine.GetPartEntity(uid, AcceleratorParts.PowerBox), out var powerConsumer))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var powerDraw = comp.BasePowerDraw;
|
var powerDraw = comp.BasePowerDraw;
|
||||||
@@ -244,30 +255,35 @@ public sealed partial class ParticleAcceleratorSystem
|
|||||||
var draw = 0f;
|
var draw = 0f;
|
||||||
var receive = 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;
|
draw = powerConsumer.DrawRate;
|
||||||
receive = powerConsumer.ReceivedPower;
|
receive = powerConsumer.ReceivedPower;
|
||||||
}
|
}
|
||||||
|
|
||||||
_uiSystem.SetUiState(uid,
|
if (!TryComp<MultipartMachineComponent>(uid, out var machineComp))
|
||||||
ParticleAcceleratorControlBoxUiKey.Key,
|
return;
|
||||||
new ParticleAcceleratorUIState(
|
|
||||||
comp.Assembled,
|
var machine = (uid, machineComp);
|
||||||
|
|
||||||
|
var uiState = new ParticleAcceleratorUIState(
|
||||||
|
machineComp.IsAssembled,
|
||||||
comp.Enabled,
|
comp.Enabled,
|
||||||
comp.SelectedStrength,
|
comp.SelectedStrength,
|
||||||
(int)draw,
|
(int)draw,
|
||||||
(int)receive,
|
(int)receive,
|
||||||
comp.StarboardEmitter != null,
|
_multipartMachine.HasPart(machine, AcceleratorParts.StarboardEmitter),
|
||||||
comp.ForeEmitter != null,
|
_multipartMachine.HasPart(machine, AcceleratorParts.ForeEmitter),
|
||||||
comp.PortEmitter != null,
|
_multipartMachine.HasPart(machine, AcceleratorParts.PortEmitter),
|
||||||
comp.PowerBox != null,
|
_multipartMachine.HasPart(machine, AcceleratorParts.PowerBox),
|
||||||
comp.FuelChamber != null,
|
_multipartMachine.HasPart(machine, AcceleratorParts.FuelChamber),
|
||||||
comp.EndCap != null,
|
_multipartMachine.HasPart(machine, AcceleratorParts.EndCap),
|
||||||
comp.InterfaceDisabled,
|
comp.InterfaceDisabled,
|
||||||
comp.MaxStrength,
|
comp.MaxStrength,
|
||||||
comp.StrengthLocked
|
comp.StrengthLocked
|
||||||
));
|
);
|
||||||
|
|
||||||
|
_uiSystem.SetUiState(uid, ParticleAcceleratorControlBoxUiKey.Key, uiState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateAppearance(EntityUid uid, ParticleAcceleratorControlBoxComponent? comp = null, AppearanceComponent? appearance = null)
|
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;
|
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)
|
// UpdatePartVisualState(ControlBox); (We are the control box)
|
||||||
if (controller.FuelChamber.HasValue)
|
if (_multipartMachine.TryGetPartEntity(machine, AcceleratorParts.FuelChamber, out var fuelChamber))
|
||||||
_appearanceSystem.SetData(controller.FuelChamber!.Value, ParticleAcceleratorVisuals.VisualState, state);
|
_appearanceSystem.SetData(fuelChamber.Value, ParticleAcceleratorVisuals.VisualState, state);
|
||||||
if (controller.PowerBox.HasValue)
|
if (_multipartMachine.TryGetPartEntity(machine, AcceleratorParts.PowerBox, out var powerBox))
|
||||||
_appearanceSystem.SetData(controller.PowerBox!.Value, ParticleAcceleratorVisuals.VisualState, state);
|
_appearanceSystem.SetData(powerBox.Value, ParticleAcceleratorVisuals.VisualState, state);
|
||||||
if (controller.PortEmitter.HasValue)
|
if (_multipartMachine.TryGetPartEntity(machine, AcceleratorParts.PortEmitter, out var portEmitter))
|
||||||
_appearanceSystem.SetData(controller.PortEmitter!.Value, ParticleAcceleratorVisuals.VisualState, state);
|
_appearanceSystem.SetData(portEmitter.Value, ParticleAcceleratorVisuals.VisualState, state);
|
||||||
if (controller.ForeEmitter.HasValue)
|
if (_multipartMachine.TryGetPartEntity(machine, AcceleratorParts.ForeEmitter, out var foreEmitter))
|
||||||
_appearanceSystem.SetData(controller.ForeEmitter!.Value, ParticleAcceleratorVisuals.VisualState, state);
|
_appearanceSystem.SetData(foreEmitter.Value, ParticleAcceleratorVisuals.VisualState, state);
|
||||||
if (controller.StarboardEmitter.HasValue)
|
if (_multipartMachine.TryGetPartEntity(machine, AcceleratorParts.StarboardEmitter, out var starboardEmitter))
|
||||||
_appearanceSystem.SetData(controller.StarboardEmitter!.Value, ParticleAcceleratorVisuals.VisualState, state);
|
_appearanceSystem.SetData(starboardEmitter.Value, ParticleAcceleratorVisuals.VisualState, state);
|
||||||
//no endcap because it has no powerlevel-sprites
|
//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)
|
UpdatePowerDraw(ent, ent.Comp);
|
||||||
yield return comp.FuelChamber.Value;
|
UpdateUI(ent, ent.Comp);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ent.Comp.Powered)
|
||||||
|
{
|
||||||
|
SwitchOff(ent, args.User, ent.Comp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateAppearance(ent, ent.Comp);
|
||||||
|
UpdateUI(ent, ent.Comp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnComponentStartup(EntityUid uid, ParticleAcceleratorControlBoxComponent comp, ComponentStartup args)
|
// 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 (TryComp<ParticleAcceleratorPartComponent>(uid, out var part))
|
if (key is AcceleratorParts.EndCap)
|
||||||
part.Master = uid;
|
continue; // No endcap powerlevel-sprites
|
||||||
|
|
||||||
|
_appearanceSystem.SetData(part, ParticleAcceleratorVisuals.VisualState, ParticleAcceleratorVisualState.Unpowered);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +384,7 @@ public sealed partial class ParticleAcceleratorSystem
|
|||||||
|
|
||||||
if (msg.Enabled)
|
if (msg.Enabled)
|
||||||
{
|
{
|
||||||
if (comp.Assembled)
|
if (_multipartMachine.IsAssembled((uid, null)))
|
||||||
SwitchOn(uid, msg.Actor, comp);
|
SwitchOn(uid, msg.Actor, comp);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -397,9 +416,13 @@ public sealed partial class ParticleAcceleratorSystem
|
|||||||
if (TryComp<ApcPowerReceiverComponent>(uid, out var apcPower) && !apcPower.Powered)
|
if (TryComp<ApcPowerReceiverComponent>(uid, out var apcPower) && !apcPower.Powered)
|
||||||
return;
|
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)
|
public static int GetPANumericalLevel(ParticleAcceleratorPowerState state)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Server.ParticleAccelerator.Components;
|
using Content.Server.ParticleAccelerator.Components;
|
||||||
using Content.Server.Singularity.Components;
|
using Content.Server.Singularity.Components;
|
||||||
|
using Content.Shared.ParticleAccelerator.Components;
|
||||||
using Content.Shared.Projectiles;
|
using Content.Shared.Projectiles;
|
||||||
using Content.Shared.Singularity.Components;
|
using Content.Shared.Singularity.Components;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using Content.Server.ParticleAccelerator.Components;
|
using Content.Server.ParticleAccelerator.Components;
|
||||||
using Content.Server.Power.EntitySystems;
|
using Content.Server.Power.EntitySystems;
|
||||||
|
using Content.Shared.Machines.Components;
|
||||||
|
using Content.Shared.ParticleAccelerator.Components;
|
||||||
|
|
||||||
namespace Content.Server.ParticleAccelerator.EntitySystems;
|
namespace Content.Server.ParticleAccelerator.EntitySystems;
|
||||||
|
|
||||||
@@ -12,7 +14,7 @@ public sealed partial class ParticleAcceleratorSystem
|
|||||||
|
|
||||||
private void PowerBoxReceivedChanged(EntityUid uid, ParticleAcceleratorPowerBoxComponent component, ref PowerConsumerReceivedChanged args)
|
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;
|
return;
|
||||||
if (!TryComp<ParticleAcceleratorControlBoxComponent>(part.Master, out var controller))
|
if (!TryComp<ParticleAcceleratorControlBoxComponent>(part.Master, out var controller))
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.Projectiles;
|
using Content.Server.Projectiles;
|
||||||
|
using Content.Server.Machines.EntitySystems;
|
||||||
using Robust.Shared.Physics.Systems;
|
using Robust.Shared.Physics.Systems;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
@@ -19,13 +20,12 @@ public sealed partial class ParticleAcceleratorSystem : EntitySystem
|
|||||||
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
|
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
[Dependency] private readonly MultipartMachineSystem _multipartMachine = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
InitializeControlBoxSystem();
|
InitializeControlBoxSystem();
|
||||||
InitializePartSystem();
|
|
||||||
InitializePowerBoxSystem();
|
InitializePowerBoxSystem();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Content.Server.Machines.EntitySystems;
|
||||||
using Content.Server.ParticleAccelerator.Components;
|
using Content.Server.ParticleAccelerator.Components;
|
||||||
using Content.Server.ParticleAccelerator.EntitySystems;
|
using Content.Server.ParticleAccelerator.EntitySystems;
|
||||||
using Content.Server.Wires;
|
using Content.Server.Wires;
|
||||||
@@ -38,10 +39,11 @@ public sealed partial class ParticleAcceleratorPowerWireAction : ComponentWireAc
|
|||||||
public override void Pulse(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
|
public override void Pulse(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
|
||||||
{
|
{
|
||||||
var paSystem = EntityManager.System<ParticleAcceleratorSystem>();
|
var paSystem = EntityManager.System<ParticleAcceleratorSystem>();
|
||||||
|
var multipartMachine = EntityManager.System<MultipartMachineSystem>();
|
||||||
|
|
||||||
if (controller.Enabled)
|
if (controller.Enabled)
|
||||||
paSystem.SwitchOff(wire.Owner, user, controller);
|
paSystem.SwitchOff(wire.Owner, user, controller);
|
||||||
else if (controller.Assembled)
|
else if (multipartMachine.IsAssembled((wire.Owner, null)))
|
||||||
paSystem.SwitchOn(wire.Owner, user, controller);
|
paSystem.SwitchOn(wire.Owner, user, controller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
|
using Content.Server.Machines.EntitySystems;
|
||||||
using Content.Server.ParticleAccelerator.Components;
|
using Content.Server.ParticleAccelerator.Components;
|
||||||
using Content.Server.ParticleAccelerator.EntitySystems;
|
using Content.Server.ParticleAccelerator.EntitySystems;
|
||||||
using Content.Server.Singularity.Components;
|
using Content.Server.Singularity.Components;
|
||||||
using Content.Server.Singularity.EntitySystems;
|
using Content.Server.Singularity.EntitySystems;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
|
using Content.Shared.Machines.Components;
|
||||||
using Content.Shared.Singularity.Components;
|
using Content.Shared.Singularity.Components;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
|
|
||||||
@@ -45,12 +47,15 @@ namespace Content.Server.Singularity
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup PA
|
// Setup PA
|
||||||
|
var multipartMachineManager = entitySystemManager.GetEntitySystem<MultipartMachineSystem>();
|
||||||
var paSystem = entitySystemManager.GetEntitySystem<ParticleAcceleratorSystem>();
|
var paSystem = entitySystemManager.GetEntitySystem<ParticleAcceleratorSystem>();
|
||||||
var paQuery = entityManager.EntityQueryEnumerator<ParticleAcceleratorControlBoxComponent>();
|
var paQuery = entityManager.EntityQueryEnumerator<ParticleAcceleratorControlBoxComponent>();
|
||||||
while (paQuery.MoveNext(out var paId, out var paControl))
|
while (paQuery.MoveNext(out var paId, out var paControl))
|
||||||
{
|
{
|
||||||
paSystem.RescanParts(paId, controller: paControl);
|
if (!entityManager.TryGetComponent<MultipartMachineComponent>(paId, out var machine))
|
||||||
if (!paControl.Assembled)
|
continue;
|
||||||
|
|
||||||
|
if (!multipartMachineManager.Rescan((paId, machine)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
paSystem.SetStrength(paId, ParticleAcceleratorPowerState.Level0, comp: paControl);
|
paSystem.SetStrength(paId, ParticleAcceleratorPowerState.Level0, comp: paControl);
|
||||||
|
|||||||
103
Content.Shared/Machines/Components/MultipartMachineComponent.cs
Normal file
103
Content.Shared/Machines/Components/MultipartMachineComponent.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using Content.Shared.Machines.EntitySystems;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.Machines.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks an entity as being the owner of a multipart machine.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)]
|
||||||
|
[Access(typeof(SharedMultipartMachineSystem))]
|
||||||
|
public sealed partial class MultipartMachineComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Dictionary of Enum values to specific parts of this machine.
|
||||||
|
/// Each key can be specified as 'enum.<EnumName>.<EnumValue>` in Yaml.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public Dictionary<Enum, MachinePart> Parts = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this multipart machine is assembled or not.
|
||||||
|
/// Optional parts are not taken into account.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool IsAssembled = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag for whether the client side system is allowed to show
|
||||||
|
/// ghosts of missing machine parts.
|
||||||
|
/// Controlled/Used by the client side.
|
||||||
|
/// </summary>
|
||||||
|
public List<EntityUid> Ghosts = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataDefinition]
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed partial class MachinePart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Component type that is expected for this part to have
|
||||||
|
/// to be considered a "Part" of the machine.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true, customTypeSerializer: typeof(ComponentNameSerializer))]
|
||||||
|
public string Component = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expected offset to find this machine at.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public Vector2i Offset;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this part is required for the machine to be
|
||||||
|
/// considered "assembled", or is considered an optional extra.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool Optional = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ID of prototype, used to show sprite and description of part, when user examines the machine and there
|
||||||
|
/// is no matched entity. Can reference dummy entities to give more detailed descriptions.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntProtoId? GhostProto = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expected rotation for this machine to have.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public Angle Rotation = Angle.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Network entity, used to inform clients and update their side of the component
|
||||||
|
/// locally.
|
||||||
|
/// Use the Entity attribute if you wish to get which entity is actually bound to this part.
|
||||||
|
/// </summary>
|
||||||
|
public NetEntity? NetEntity = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entity associated with this part.
|
||||||
|
/// Not null when an entity is successfully matched to the part and null otherwise.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, NonSerialized]
|
||||||
|
public EntityUid? Entity = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expected graph for this part to use as part of its construction.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntProtoId Graph;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expected node for this part to be in, on the graph.
|
||||||
|
/// Used to determine when a construct-able object has been
|
||||||
|
/// assembled or disassembled.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public string ExpectedNode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
|
namespace Content.Shared.Machines.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Component for marking entities as part of a multipart machine.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||||
|
public sealed partial class MultipartMachinePartComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Links to the entity which holds the MultipartMachineComponent.
|
||||||
|
/// Useful so that entities that know which machine they are a part of.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public EntityUid? Master = null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Content.Shared.Machines.Components;
|
||||||
|
|
||||||
|
namespace Content.Shared.Machines.EntitySystems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shared handling of multipart machines.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class SharedMultipartMachineSystem : EntitySystem
|
||||||
|
{
|
||||||
|
protected EntityQuery<TransformComponent> XformQuery;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
XformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether each non-optional part of the machine has a matched entity
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Entity to check the assembled state of.</param>
|
||||||
|
/// <returns>True if all non-optional parts have a matching entity, false otherwise.</returns>
|
||||||
|
public bool IsAssembled(Entity<MultipartMachineComponent?> ent)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var part in ent.Comp.Parts.Values)
|
||||||
|
{
|
||||||
|
if (!part.Entity.HasValue && !part.Optional)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether a machine has a specifed EntityUid bound to one of its parts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="machine">Entity, which might have a multpart machine attached, to use for the query.</param>
|
||||||
|
/// <param name="entity">EntityUid to search for.</param>
|
||||||
|
/// <returns>True if any part has the specified EntityUid, false otherwise.</returns>
|
||||||
|
public bool HasPartEntity(Entity<MultipartMachineComponent?> machine, EntityUid entity)
|
||||||
|
{
|
||||||
|
if (!Resolve(machine, ref machine.Comp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var part in machine.Comp.Parts.Values)
|
||||||
|
{
|
||||||
|
if (part.Entity.HasValue && part.Entity.Value == entity)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the EntityUid for the entity bound to a specific part, if one exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Entity, which might have a multipart machine attached, to use for the query.</param>
|
||||||
|
/// <param name="part">Enum value for the part to find, must match the value specified in YAML.</param>
|
||||||
|
/// <returns>May contain the resolved EntityUid for the specified part, null otherwise.</returns>
|
||||||
|
public EntityUid? GetPartEntity(Entity<MultipartMachineComponent?> ent, Enum part)
|
||||||
|
{
|
||||||
|
if (!TryGetPartEntity(ent, part, out var entity))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the EntityUid for the entity bound to a specific part, if one exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Entity, which might have a multipart machine attached, to use for the query.</param>
|
||||||
|
/// <param name="part">Enum for the part to find, must match the value specified in YAML.</param>
|
||||||
|
/// <param name="entity">Out var which may contain the matched EntityUid for the specified part.</param>
|
||||||
|
/// <returns>True if the part is found and has a matched entity, false otherwise.</returns>
|
||||||
|
public bool TryGetPartEntity(
|
||||||
|
Entity<MultipartMachineComponent?> ent,
|
||||||
|
Enum part,
|
||||||
|
[NotNullWhen(true)] out EntityUid? entity
|
||||||
|
)
|
||||||
|
{
|
||||||
|
entity = null;
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (ent.Comp.Parts.TryGetValue(part, out var value) && value.Entity.HasValue)
|
||||||
|
{
|
||||||
|
entity = value.Entity.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if a machine has an entity bound to a specific part
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Entity, which might have a multipart machine attached, to use for the query.</param>
|
||||||
|
/// <param name="part">Enum for the part to find.</param>
|
||||||
|
/// <returns>True if the specific part has a entity bound to it, false otherwise.</returns>
|
||||||
|
public bool HasPart(Entity<MultipartMachineComponent?> ent, Enum part)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!ent.Comp.Parts.TryGetValue(part, out var value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return value.Entity != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Content.Shared/Machines/Events/MultipartMachineEvents.cs
Normal file
21
Content.Shared/Machines/Events/MultipartMachineEvents.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
namespace Content.Shared.Machines.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This event is raised when the assembled state of a Multipart machine changes.
|
||||||
|
/// This includes when optional parts are found, parts become unanchored, or move
|
||||||
|
/// within a construction graph.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Entity">Entity that is bound to the multipart machine.</param>
|
||||||
|
/// <param name="IsAssembled">Assembled state of the machine.</param>
|
||||||
|
/// <param name="User">Optional user that may have caused the assembly state to change.</param>
|
||||||
|
/// <param name="PartsAdded">Dictionary of keys to entities of parts that have been added to this machine.</param>
|
||||||
|
/// <param name="PartsRemoved">Dictionary of keys to entities of parts that have been removed from this machine.</param>
|
||||||
|
[ByRefEvent]
|
||||||
|
public record struct MultipartMachineAssemblyStateChanged(
|
||||||
|
EntityUid Entity,
|
||||||
|
bool IsAssembled,
|
||||||
|
EntityUid? User,
|
||||||
|
Dictionary<Enum, EntityUid> PartsAdded,
|
||||||
|
Dictionary<Enum, EntityUid> PartsRemoved)
|
||||||
|
{
|
||||||
|
}
|
||||||
16
Content.Shared/ParticleAccelerator/AcceleratorParts.cs
Normal file
16
Content.Shared/ParticleAccelerator/AcceleratorParts.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.ParticleAccelerator;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum AcceleratorParts : byte
|
||||||
|
{
|
||||||
|
EndCap,
|
||||||
|
FuelChamber,
|
||||||
|
PowerBox,
|
||||||
|
PortEmitter,
|
||||||
|
ForeEmitter,
|
||||||
|
StarboardEmitter
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.ParticleAccelerator.Components;
|
namespace Content.Shared.ParticleAccelerator.Components;
|
||||||
|
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class ParticleAcceleratorEmitterComponent : Component
|
public sealed partial class ParticleAcceleratorEmitterComponent : Component
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Content.Server.ParticleAccelerator.Components;
|
namespace Content.Shared.ParticleAccelerator.Components;
|
||||||
|
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class ParticleAcceleratorEndCapComponent : Component
|
public sealed partial class ParticleAcceleratorEndCapComponent : Component
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Content.Server.ParticleAccelerator.Components;
|
namespace Content.Shared.ParticleAccelerator.Components;
|
||||||
|
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class ParticleAcceleratorFuelChamberComponent : Component
|
public sealed partial class ParticleAcceleratorFuelChamberComponent : Component
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Content.Server.ParticleAccelerator.Components;
|
namespace Content.Shared.ParticleAccelerator.Components;
|
||||||
|
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class ParticleAcceleratorPowerBoxComponent : Component
|
public sealed partial class ParticleAcceleratorPowerBoxComponent : Component
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Spawn on client side when users examine a multipart machine
|
||||||
|
# If a sprite is given then the default component's value will be overriden
|
||||||
|
- type: entity
|
||||||
|
id: MultipartMachineGhost
|
||||||
|
categories: [ HideSpawnMenu ]
|
||||||
|
components:
|
||||||
|
- type: MultipartMachineGhost
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Markers/cross.rsi
|
||||||
|
layers:
|
||||||
|
- state: green
|
||||||
|
color: "#FFFFFF80"
|
||||||
|
- type: TimedDespawn
|
||||||
|
lifetime: 5
|
||||||
|
- type: Clickable
|
||||||
|
- type: Tag
|
||||||
|
tags:
|
||||||
|
- HideContextMenu
|
||||||
@@ -43,7 +43,6 @@
|
|||||||
map: [ "enum.ParticleAcceleratorVisualLayers.Unlit" ]
|
map: [ "enum.ParticleAcceleratorVisualLayers.Unlit" ]
|
||||||
shader: unshaded
|
shader: unshaded
|
||||||
visible: false
|
visible: false
|
||||||
- type: ParticleAcceleratorPart
|
|
||||||
- type: ParticleAcceleratorPartVisuals
|
- type: ParticleAcceleratorPartVisuals
|
||||||
stateBase: unlit
|
stateBase: unlit
|
||||||
- type: Construction
|
- type: Construction
|
||||||
|
|||||||
@@ -27,6 +27,50 @@
|
|||||||
layoutId: ParticleAccelerator
|
layoutId: ParticleAccelerator
|
||||||
- type: AccessReader
|
- type: AccessReader
|
||||||
access: [["Engineering"]]
|
access: [["Engineering"]]
|
||||||
|
- type: MultipartMachine
|
||||||
|
parts:
|
||||||
|
enum.AcceleratorParts.EndCap:
|
||||||
|
component: ParticleAcceleratorEndCap
|
||||||
|
offset: 1, -1
|
||||||
|
rotation: -90
|
||||||
|
ghostProto: ParticleAcceleratorEndCap
|
||||||
|
graph: ParticleAcceleratorEndCap
|
||||||
|
expectedNode: completed
|
||||||
|
enum.AcceleratorParts.FuelChamber:
|
||||||
|
component: ParticleAcceleratorFuelChamber
|
||||||
|
offset: 0, -1
|
||||||
|
rotation: -90
|
||||||
|
ghostProto: ParticleAcceleratorFuelChamber
|
||||||
|
graph: ParticleAcceleratorFuelChamber
|
||||||
|
expectedNode: completed
|
||||||
|
enum.AcceleratorParts.PowerBox:
|
||||||
|
component: ParticleAcceleratorPowerBox
|
||||||
|
offset: -1, -1
|
||||||
|
rotation: -90
|
||||||
|
ghostProto: ParticleAcceleratorPowerBox
|
||||||
|
graph: ParticleAcceleratorPowerBox
|
||||||
|
expectedNode: completed
|
||||||
|
enum.AcceleratorParts.PortEmitter:
|
||||||
|
component: ParticleAcceleratorEmitter
|
||||||
|
offset: -2, -2
|
||||||
|
rotation: -90
|
||||||
|
ghostProto: ParticleAcceleratorEmitterPort
|
||||||
|
graph: ParticleAcceleratorEmitterPort
|
||||||
|
expectedNode: completed
|
||||||
|
enum.AcceleratorParts.ForeEmitter:
|
||||||
|
component: ParticleAcceleratorEmitter
|
||||||
|
offset: -2, -1
|
||||||
|
rotation: -90
|
||||||
|
ghostProto: ParticleAcceleratorEmitterFore
|
||||||
|
graph: ParticleAcceleratorEmitterFore
|
||||||
|
expectedNode: completed
|
||||||
|
enum.AcceleratorParts.StarboardEmitter:
|
||||||
|
component: ParticleAcceleratorEmitter
|
||||||
|
offset: -2, 0
|
||||||
|
rotation: -90
|
||||||
|
ghostProto: ParticleAcceleratorEmitterStarboard
|
||||||
|
graph: ParticleAcceleratorEmitterStarboard
|
||||||
|
expectedNode: completed
|
||||||
|
|
||||||
# Unfinished
|
# Unfinished
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
emitterType: Port
|
emitterType: Port
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: ParticleAcceleratorEmitterPort
|
graph: ParticleAcceleratorEmitterPort
|
||||||
|
- type: MultipartMachinePart
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: ParticleAcceleratorFinishedPart
|
parent: ParticleAcceleratorFinishedPart
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
emitterType: Fore
|
emitterType: Fore
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: ParticleAcceleratorEmitterFore
|
graph: ParticleAcceleratorEmitterFore
|
||||||
|
- type: MultipartMachinePart
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: ParticleAcceleratorFinishedPart
|
parent: ParticleAcceleratorFinishedPart
|
||||||
@@ -36,6 +38,7 @@
|
|||||||
emitterType: Starboard
|
emitterType: Starboard
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: ParticleAcceleratorEmitterStarboard
|
graph: ParticleAcceleratorEmitterStarboard
|
||||||
|
- type: MultipartMachinePart
|
||||||
|
|
||||||
# Unfinished
|
# Unfinished
|
||||||
|
|
||||||
@@ -50,6 +53,7 @@
|
|||||||
sprite: Structures/Power/Generation/PA/emitter_port.rsi
|
sprite: Structures/Power/Generation/PA/emitter_port.rsi
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: ParticleAcceleratorEmitterPort
|
graph: ParticleAcceleratorEmitterPort
|
||||||
|
- type: MultipartMachinePart
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: ParticleAcceleratorUnfinishedBase
|
parent: ParticleAcceleratorUnfinishedBase
|
||||||
@@ -62,6 +66,7 @@
|
|||||||
sprite: Structures/Power/Generation/PA/emitter_fore.rsi
|
sprite: Structures/Power/Generation/PA/emitter_fore.rsi
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: ParticleAcceleratorEmitterFore
|
graph: ParticleAcceleratorEmitterFore
|
||||||
|
- type: MultipartMachinePart
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: ParticleAcceleratorUnfinishedBase
|
parent: ParticleAcceleratorUnfinishedBase
|
||||||
@@ -74,3 +79,4 @@
|
|||||||
sprite: Structures/Power/Generation/PA/emitter_starboard.rsi
|
sprite: Structures/Power/Generation/PA/emitter_starboard.rsi
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: ParticleAcceleratorEmitterStarboard
|
graph: ParticleAcceleratorEmitterStarboard
|
||||||
|
- type: MultipartMachinePart
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
- type: ParticleAcceleratorEndCap
|
- type: ParticleAcceleratorEndCap
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: ParticleAcceleratorEndCap
|
graph: ParticleAcceleratorEndCap
|
||||||
|
- type: MultipartMachinePart
|
||||||
|
|
||||||
# Unfinished
|
# Unfinished
|
||||||
|
|
||||||
@@ -26,3 +27,4 @@
|
|||||||
sprite: Structures/Power/Generation/PA/end_cap.rsi
|
sprite: Structures/Power/Generation/PA/end_cap.rsi
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: ParticleAcceleratorEndCap
|
graph: ParticleAcceleratorEndCap
|
||||||
|
- type: MultipartMachinePart
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
- type: ParticleAcceleratorFuelChamber
|
- type: ParticleAcceleratorFuelChamber
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: ParticleAcceleratorFuelChamber
|
graph: ParticleAcceleratorFuelChamber
|
||||||
|
- type: MultipartMachinePart
|
||||||
|
|
||||||
# Unfinished
|
# Unfinished
|
||||||
|
|
||||||
@@ -23,3 +24,4 @@
|
|||||||
sprite: Structures/Power/Generation/PA/fuel_chamber.rsi
|
sprite: Structures/Power/Generation/PA/fuel_chamber.rsi
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: ParticleAcceleratorFuelChamber
|
graph: ParticleAcceleratorFuelChamber
|
||||||
|
- type: MultipartMachinePart
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
nodeGroupID: MVPower
|
nodeGroupID: MVPower
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: ParticleAcceleratorPowerBox
|
graph: ParticleAcceleratorPowerBox
|
||||||
|
- type: MultipartMachinePart
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: ParticleAcceleratorUnfinishedBase
|
parent: ParticleAcceleratorUnfinishedBase
|
||||||
@@ -29,3 +30,4 @@
|
|||||||
sprite: Structures/Power/Generation/PA/power_box.rsi
|
sprite: Structures/Power/Generation/PA/power_box.rsi
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: ParticleAcceleratorPowerBox
|
graph: ParticleAcceleratorPowerBox
|
||||||
|
- type: MultipartMachinePart
|
||||||
|
|||||||
Reference in New Issue
Block a user