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/>
|
||||
<ui:PASegmentControl Name="EndCapTexture" BaseState="end_cap"/>
|
||||
<Control/>
|
||||
<ui:PASegmentControl Name="ControlBoxTexture" BaseState="control_box"/>
|
||||
<ui:PASegmentControl Name="ControlBoxTexture" BaseState="control_box" DefaultVisible="True"/>
|
||||
<ui:PASegmentControl Name="FuelChamberTexture" BaseState="fuel_chamber"/>
|
||||
<Control/>
|
||||
<Control/>
|
||||
|
||||
@@ -268,6 +268,7 @@ public sealed class PASegmentControl : Control
|
||||
private RSI? _rsi;
|
||||
|
||||
public string BaseState { get; set; } = "control_box";
|
||||
public bool DefaultVisible { get; set; } = false;
|
||||
|
||||
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;
|
||||
MinSize = _rsi.Size;
|
||||
_base.Texture = _rsi["completed"].Frame0;
|
||||
|
||||
SetVisible(DefaultVisible);
|
||||
_unlit.Visible = DefaultVisible;
|
||||
}
|
||||
|
||||
public void SetPowerState(ParticleAcceleratorUIState state, bool exists)
|
||||
{
|
||||
_base.ShaderOverride = exists ? null : _greyScaleShader;
|
||||
_base.ModulateSelfOverride = exists ? null : new Color(127, 127, 127);
|
||||
SetVisible(exists);
|
||||
|
||||
if (!state.Enabled || !exists)
|
||||
{
|
||||
@@ -319,4 +322,23 @@ public sealed class PASegmentControl : Control
|
||||
|
||||
_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.
|
||||
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;
|
||||
|
||||
if (performActions)
|
||||
@@ -281,6 +281,7 @@ namespace Content.Server.Construction
|
||||
/// <param name="userUid">An optional user entity, for actions.</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="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="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,
|
||||
@@ -288,6 +289,7 @@ namespace Content.Server.Construction
|
||||
/// <returns>The new entity, or null if the method did not succeed.</returns>
|
||||
private EntityUid? ChangeEntity(EntityUid uid, EntityUid? userUid, string newEntity,
|
||||
ConstructionComponent? construction = null,
|
||||
string? previousNode = null,
|
||||
MetaDataComponent? metaData = null,
|
||||
TransformComponent? transform = null,
|
||||
ContainerManagerComponent? containerManager = null)
|
||||
@@ -407,6 +409,11 @@ namespace Content.Server.Construction
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -453,4 +460,16 @@ namespace Content.Server.Construction
|
||||
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",
|
||||
"LightFade",
|
||||
"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]
|
||||
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>
|
||||
/// Whether the PA is currently set to fire at the console.
|
||||
/// Requires <see cref="Assembled"/> to be true.
|
||||
@@ -40,12 +34,6 @@ public sealed partial class ParticleAcceleratorControlBoxComponent : Component
|
||||
[ViewVariables]
|
||||
public bool Firing = false;
|
||||
|
||||
/// <summary>
|
||||
/// Block re-entrant rescanning.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool CurrentlyRescanning = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the PA is currently firing or charging to fire.
|
||||
/// Bounded by <see cref="ParticleAcceleratorPowerState.Standby"/> and <see cref="MaxStrength"/>.
|
||||
@@ -61,48 +49,6 @@ public sealed partial class ParticleAcceleratorControlBoxComponent : Component
|
||||
[ViewVariables]
|
||||
public ParticleAcceleratorPowerState MaxStrength = ParticleAcceleratorPowerState.Level2;
|
||||
|
||||
/// <summary>
|
||||
/// The power supply unit of the assembled particle accelerator.
|
||||
/// Implies the existance of a <see cref="ParticleAcceleratorPowerBoxComponent"/> attached to this entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? PowerBox;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the PA is currently firing or charging to fire.
|
||||
/// Implies the existance of a <see cref="ParticleAcceleratorEndCapComponent"/> attached to this entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? EndCap;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the PA is currently firing or charging to fire.
|
||||
/// Implies the existance of a <see cref="ParticleAcceleratorFuelChamberComponent"/> attached to this entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? FuelChamber;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the PA is currently firing or charging to fire.
|
||||
/// Implies the existance of a <see cref="ParticleAcceleratorEmitterComponent"/> attached to this entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? PortEmitter;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the PA is currently firing or charging to fire.
|
||||
/// Implies the existance of a <see cref="ParticleAcceleratorEmitterComponent"/> attached to this entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? ForeEmitter;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the PA is currently firing or charging to fire.
|
||||
/// Implies the existance of a <see cref="ParticleAcceleratorEmitterComponent"/> attached to this entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? StarboardEmitter;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of power the particle accelerator must be provided with relative to the expected power draw to function.
|
||||
/// </summary>
|
||||
|
||||
@@ -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.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)
|
||||
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);
|
||||
}
|
||||
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))
|
||||
part.Master = uid;
|
||||
if (key is AcceleratorParts.EndCap)
|
||||
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 (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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Machines.EntitySystems;
|
||||
using Content.Server.ParticleAccelerator.Components;
|
||||
using Content.Server.ParticleAccelerator.EntitySystems;
|
||||
using Content.Server.Wires;
|
||||
@@ -38,10 +39,11 @@ public sealed partial class ParticleAcceleratorPowerWireAction : ComponentWireAc
|
||||
public override void Pulse(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
|
||||
{
|
||||
var paSystem = EntityManager.System<ParticleAcceleratorSystem>();
|
||||
var multipartMachine = EntityManager.System<MultipartMachineSystem>();
|
||||
|
||||
if (controller.Enabled)
|
||||
paSystem.SwitchOff(wire.Owner, user, controller);
|
||||
else if (controller.Assembled)
|
||||
else if (multipartMachine.IsAssembled((wire.Owner, null)))
|
||||
paSystem.SwitchOn(wire.Owner, user, controller);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Machines.EntitySystems;
|
||||
using Content.Server.ParticleAccelerator.Components;
|
||||
using Content.Server.ParticleAccelerator.EntitySystems;
|
||||
using Content.Server.Singularity.Components;
|
||||
using Content.Server.Singularity.EntitySystems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Machines.Components;
|
||||
using Content.Shared.Singularity.Components;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
@@ -45,12 +47,15 @@ namespace Content.Server.Singularity
|
||||
}
|
||||
|
||||
// Setup PA
|
||||
var multipartMachineManager = entitySystemManager.GetEntitySystem<MultipartMachineSystem>();
|
||||
var paSystem = entitySystemManager.GetEntitySystem<ParticleAcceleratorSystem>();
|
||||
var paQuery = entityManager.EntityQueryEnumerator<ParticleAcceleratorControlBoxComponent>();
|
||||
while (paQuery.MoveNext(out var paId, out var paControl))
|
||||
{
|
||||
paSystem.RescanParts(paId, controller: paControl);
|
||||
if (!paControl.Assembled)
|
||||
if (!entityManager.TryGetComponent<MultipartMachineComponent>(paId, out var machine))
|
||||
continue;
|
||||
|
||||
if (!multipartMachineManager.Rescan((paId, machine)))
|
||||
continue;
|
||||
|
||||
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;
|
||||
|
||||
namespace Content.Server.ParticleAccelerator.Components;
|
||||
namespace Content.Shared.ParticleAccelerator.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class ParticleAcceleratorEmitterComponent : Component
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Content.Server.ParticleAccelerator.Components;
|
||||
namespace Content.Shared.ParticleAccelerator.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class ParticleAcceleratorEndCapComponent : Component
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Content.Server.ParticleAccelerator.Components;
|
||||
namespace Content.Shared.ParticleAccelerator.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class ParticleAcceleratorFuelChamberComponent : Component
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Content.Server.ParticleAccelerator.Components;
|
||||
namespace Content.Shared.ParticleAccelerator.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
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" ]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
- type: ParticleAcceleratorPart
|
||||
- type: ParticleAcceleratorPartVisuals
|
||||
stateBase: unlit
|
||||
- type: Construction
|
||||
|
||||
@@ -27,6 +27,50 @@
|
||||
layoutId: ParticleAccelerator
|
||||
- type: AccessReader
|
||||
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
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
emitterType: Port
|
||||
- type: Construction
|
||||
graph: ParticleAcceleratorEmitterPort
|
||||
- type: MultipartMachinePart
|
||||
|
||||
- type: entity
|
||||
parent: ParticleAcceleratorFinishedPart
|
||||
@@ -23,6 +24,7 @@
|
||||
emitterType: Fore
|
||||
- type: Construction
|
||||
graph: ParticleAcceleratorEmitterFore
|
||||
- type: MultipartMachinePart
|
||||
|
||||
- type: entity
|
||||
parent: ParticleAcceleratorFinishedPart
|
||||
@@ -36,6 +38,7 @@
|
||||
emitterType: Starboard
|
||||
- type: Construction
|
||||
graph: ParticleAcceleratorEmitterStarboard
|
||||
- type: MultipartMachinePart
|
||||
|
||||
# Unfinished
|
||||
|
||||
@@ -50,6 +53,7 @@
|
||||
sprite: Structures/Power/Generation/PA/emitter_port.rsi
|
||||
- type: Construction
|
||||
graph: ParticleAcceleratorEmitterPort
|
||||
- type: MultipartMachinePart
|
||||
|
||||
- type: entity
|
||||
parent: ParticleAcceleratorUnfinishedBase
|
||||
@@ -62,6 +66,7 @@
|
||||
sprite: Structures/Power/Generation/PA/emitter_fore.rsi
|
||||
- type: Construction
|
||||
graph: ParticleAcceleratorEmitterFore
|
||||
- type: MultipartMachinePart
|
||||
|
||||
- type: entity
|
||||
parent: ParticleAcceleratorUnfinishedBase
|
||||
@@ -74,3 +79,4 @@
|
||||
sprite: Structures/Power/Generation/PA/emitter_starboard.rsi
|
||||
- type: Construction
|
||||
graph: ParticleAcceleratorEmitterStarboard
|
||||
- type: MultipartMachinePart
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
- type: ParticleAcceleratorEndCap
|
||||
- type: Construction
|
||||
graph: ParticleAcceleratorEndCap
|
||||
- type: MultipartMachinePart
|
||||
|
||||
# Unfinished
|
||||
|
||||
@@ -26,3 +27,4 @@
|
||||
sprite: Structures/Power/Generation/PA/end_cap.rsi
|
||||
- type: Construction
|
||||
graph: ParticleAcceleratorEndCap
|
||||
- type: MultipartMachinePart
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
- type: ParticleAcceleratorFuelChamber
|
||||
- type: Construction
|
||||
graph: ParticleAcceleratorFuelChamber
|
||||
- type: MultipartMachinePart
|
||||
|
||||
# Unfinished
|
||||
|
||||
@@ -23,3 +24,4 @@
|
||||
sprite: Structures/Power/Generation/PA/fuel_chamber.rsi
|
||||
- type: Construction
|
||||
graph: ParticleAcceleratorFuelChamber
|
||||
- type: MultipartMachinePart
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
nodeGroupID: MVPower
|
||||
- type: Construction
|
||||
graph: ParticleAcceleratorPowerBox
|
||||
- type: MultipartMachinePart
|
||||
|
||||
- type: entity
|
||||
parent: ParticleAcceleratorUnfinishedBase
|
||||
@@ -29,3 +30,4 @@
|
||||
sprite: Structures/Power/Generation/PA/power_box.rsi
|
||||
- type: Construction
|
||||
graph: ParticleAcceleratorPowerBox
|
||||
- type: MultipartMachinePart
|
||||
|
||||
Reference in New Issue
Block a user