using Content.Server.Climbing; using Content.Server.EUI; using Content.Server.Mind.Components; using Content.Server.Power.Components; using Content.Server.UserInterface; using Content.Shared.CharacterAppearance.Systems; using Content.Shared.Cloning; using Content.Shared.MobState.Components; using Content.Shared.Popups; using Content.Shared.Species; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Player; using Robust.Shared.Players; using Robust.Shared.Prototypes; namespace Content.Server.Cloning.Components { [RegisterComponent] public sealed class CloningPodComponent : SharedCloningPodComponent { [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IEntityManager _entities = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly EuiManager _euiManager = null!; [ViewVariables] public BoundUserInterface? UserInterface => Owner.GetUIOrNull(CloningPodUIKey.Key); [ViewVariables] public ContainerSlot BodyContainer = default!; [ViewVariables] public Mind.Mind? CapturedMind; [ViewVariables] public float CloningProgress = 0; [DataField("cloningTime")] [ViewVariables] public float CloningTime = 30f; // Used to prevent as many duplicate UI messages as possible [ViewVariables] public bool UiKnownPowerState = false; [ViewVariables(VVAccess.ReadWrite), DataField("soundCloneStart")] public SoundSpecifier? CloneStartSound = new SoundPathSpecifier("/Audio/Machines/genetics.ogg"); [ViewVariables] public CloningPodStatus Status; protected override void Initialize() { base.Initialize(); if (UserInterface != null) { UserInterface.OnReceiveMessage += OnUiReceiveMessage; } BodyContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-bodyContainer"); //TODO: write this so that it checks for a change in power events for GORE POD cases EntitySystem.Get().UpdateUserInterface(this); } protected override void OnRemove() { if (UserInterface != null) { UserInterface.OnReceiveMessage -= OnUiReceiveMessage; } base.OnRemove(); } private void UpdateAppearance() { if (_entities.TryGetComponent(Owner, out AppearanceComponent? appearance)) { appearance.SetData(CloningPodVisuals.Status, Status); } } private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) { if (obj.Message is not CloningPodUiButtonPressedMessage message || obj.Session.AttachedEntity == null) return; switch (message.Button) { case UiButton.Clone: if (BodyContainer.ContainedEntity != null) { obj.Session.AttachedEntity.Value.PopupMessageCursor(Loc.GetString("cloning-pod-component-msg-occupied")); return; } if (message.ScanId == null) { obj.Session.AttachedEntity.Value.PopupMessageCursor(Loc.GetString("cloning-pod-component-msg-no-selection")); return; } var cloningSystem = EntitySystem.Get(); if (!cloningSystem.IdToDNA.TryGetValue(message.ScanId.Value, out var dna)) { obj.Session.AttachedEntity.Value.PopupMessageCursor(Loc.GetString("cloning-pod-component-msg-bad-selection")); return; // ScanId is not in database } var mind = dna.Mind; if (cloningSystem.ClonesWaitingForMind.TryGetValue(mind, out var clone)) { if (_entities.EntityExists(clone) && _entities.TryGetComponent(clone, out var cloneState) && !cloneState.IsDead() && _entities.TryGetComponent(clone, out MindComponent? cloneMindComp) && (cloneMindComp.Mind == null || cloneMindComp.Mind == mind)) { obj.Session.AttachedEntity.Value.PopupMessageCursor(Loc.GetString("cloning-pod-component-msg-already-cloning")); return; // Mind already has clone } cloningSystem.ClonesWaitingForMind.Remove(mind); } if (mind.OwnedEntity != null && _entities.TryGetComponent(mind.OwnedEntity.Value, out var state) && !state.IsDead()) { obj.Session.AttachedEntity.Value.PopupMessageCursor(Loc.GetString("cloning-pod-component-msg-already-alive")); return; // Body controlled by mind is not dead } // Yes, we still need to track down the client because we need to open the Eui if (mind.UserId == null || !_playerManager.TryGetSessionById(mind.UserId.Value, out var client)) { obj.Session.AttachedEntity.Value.PopupMessageCursor(Loc.GetString("cloning-pod-component-msg-user-offline")); return; // If we can't track down the client, we can't offer transfer. That'd be quite bad. } // Cloning confirmed now. var speciesProto = _prototype.Index(dna.Profile.Species).Prototype; var mob = _entities.SpawnEntity(speciesProto, _entities.GetComponent(Owner).MapPosition); EntitySystem.Get().UpdateFromProfile(mob, dna.Profile); _entities.GetComponent(mob).EntityName = dna.Profile.Name; // TODO: Ideally client knows about this and plays it on its own // Send them a sound to know it happened if (CloneStartSound != null) SoundSystem.Play(CloneStartSound.GetSound(), Filter.SinglePlayer(client)); var cloneMindReturn = _entities.AddComponent(mob); cloneMindReturn.Mind = mind; cloneMindReturn.Parent = Owner; BodyContainer.Insert(mob); CapturedMind = mind; cloningSystem.ClonesWaitingForMind.Add(mind, mob); UpdateStatus(CloningPodStatus.NoMind); var acceptMessage = new AcceptCloningEui(mind); _euiManager.OpenEui(acceptMessage, client); break; case UiButton.Eject: if (BodyContainer.ContainedEntity == null) { obj.Session.AttachedEntity.Value.PopupMessageCursor(Loc.GetString("cloning-pod-component-msg-empty")); return; } if (CloningProgress < CloningTime) { obj.Session.AttachedEntity.Value.PopupMessageCursor(Loc.GetString("cloning-pod-component-msg-incomplete")); return; } Eject(); break; default: throw new ArgumentOutOfRangeException(); } } public void Eject() { if (BodyContainer.ContainedEntity is not {Valid: true} entity || CloningProgress < CloningTime) return; _entities.RemoveComponent(entity); BodyContainer.Remove(entity); CapturedMind = null; CloningProgress = 0f; UpdateStatus(CloningPodStatus.Idle); EntitySystem.Get().ForciblySetClimbing(entity, Owner); } public void UpdateStatus(CloningPodStatus status) { Status = status; UpdateAppearance(); EntitySystem.Get().UpdateUserInterface(this); } } }