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;
///
/// 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.
///
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(OnMachineExamined);
SubscribeLocalEvent(OnHandleState);
SubscribeLocalEvent(OnGhostDespawned);
}
///
/// 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.
///
/// Entity/Component that has been inspected.
/// Args for the event.
private void OnMachineExamined(Entity 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(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(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 ent, ref AfterAutoHandleStateEvent args)
{
foreach (var part in ent.Comp.Parts.Values)
{
part.Entity = part.NetEntity.HasValue ? EnsureEntity(part.NetEntity.Value, ent) : null;
}
}
///
/// 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.
///
/// Ghost entity that has been despawned.
/// Args for the event.
private void OnGhostDespawned(Entity ent, ref TimedDespawnEvent args)
{
if (!TryComp(ent.Comp.LinkedMachine, out var machine))
return;
machine.Ghosts.Remove(ent);
}
}