#nullable enable using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Content.Server.GameObjects.Components.Access; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Paper; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Interfaces.PDA; using Content.Server.Utility; using Content.Shared.GameObjects.Components.PDA; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.Verbs; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.GameObjects.EntitySystems; using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.PDA { [RegisterComponent] [ComponentReference(typeof(IActivate))] [ComponentReference(typeof(IAccess))] public class PDAComponent : SharedPDAComponent, IInteractUsing, IActivate, IUse, IAccess { [Dependency] private readonly IPDAUplinkManager _uplinkManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [ViewVariables] private ContainerSlot _idSlot = default!; [ViewVariables] private ContainerSlot _penSlot = default!; [ViewVariables] private bool _lightOn; [ViewVariables] private string? _startingIdCard = default!; [ViewVariables] private string? _startingPen = default!; [ViewVariables] public string? OwnerName { get; private set; } [ViewVariables] public IdCardComponent? ContainedID { get; private set; } [ViewVariables] public bool IdSlotEmpty => _idSlot.ContainedEntity == null; [ViewVariables] public bool PenSlotEmpty => _penSlot.ContainedEntity == null; [ViewVariables] private UplinkAccount? _syndicateUplinkAccount; [ViewVariables] private readonly PdaAccessSet _accessSet; [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(PDAUiKey.Key); public PDAComponent() { _accessSet = new PdaAccessSet(this); } public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); serializer.DataField(ref _startingIdCard, "idCard", "AssistantIDCard"); serializer.DataField(ref _startingPen, "pen", "Pen"); } public override void Initialize() { base.Initialize(); _idSlot = ContainerManagerComponent.Ensure("pda_entity_container", Owner); _penSlot = ContainerManagerComponent.Ensure("pda_pen_slot", Owner); if (UserInterface != null) { UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; } if (!string.IsNullOrEmpty(_startingIdCard)) { var idCard = _entityManager.SpawnEntity(_startingIdCard, Owner.Transform.Coordinates); var idCardComponent = idCard.GetComponent(); _idSlot.Insert(idCardComponent.Owner); ContainedID = idCardComponent; } if (!string.IsNullOrEmpty(_startingPen)) { var pen = _entityManager.SpawnEntity(_startingPen, Owner.Transform.Coordinates); _penSlot.Insert(pen); } UpdatePDAAppearance(); } private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage message) { switch (message.Message) { case PDARequestUpdateInterfaceMessage _: { UpdatePDAUserInterface(); break; } case PDAToggleFlashlightMessage _: { ToggleLight(); break; } case PDAEjectIDMessage _: { HandleIDEjection(message.Session.AttachedEntity!); break; } case PDAEjectPenMessage _: { HandlePenEjection(message.Session.AttachedEntity!); break; } case PDAUplinkBuyListingMessage buyMsg: { if (!_uplinkManager.TryPurchaseItem(_syndicateUplinkAccount, buyMsg.ItemId)) { SendNetworkMessage(new PDAUplinkInsufficientFundsMessage(), message.Session.ConnectedClient); break; } SendNetworkMessage(new PDAUplinkBuySuccessMessage(), message.Session.ConnectedClient); break; } } } private void UpdatePDAUserInterface() { var ownerInfo = new PDAIdInfoText { ActualOwnerName = OwnerName, IdOwner = ContainedID?.FullName, JobTitle = ContainedID?.JobTitle }; //Do we have an account? If so provide the info. if (_syndicateUplinkAccount != null) { var accData = new UplinkAccountData(_syndicateUplinkAccount.AccountHolder, _syndicateUplinkAccount.Balance); var listings = _uplinkManager.FetchListings.Values.ToArray(); UserInterface?.SetState(new PDAUpdateState(_lightOn, !PenSlotEmpty, ownerInfo, accData, listings)); } else { UserInterface?.SetState(new PDAUpdateState(_lightOn, !PenSlotEmpty, ownerInfo)); } UpdatePDAAppearance(); } private void UpdatePDAAppearance() { if (Owner.TryGetComponent(out AppearanceComponent? appearance)) { appearance.SetData(PDAVisuals.FlashlightLit, _lightOn); appearance.SetData(PDAVisuals.IDCardInserted, !IdSlotEmpty); } } private bool TryInsertIdCard(InteractUsingEventArgs eventArgs, IdCardComponent idCardComponent) { var item = eventArgs.Using; if (_idSlot.Contains(item)) return false; if (!eventArgs.User.TryGetComponent(out IHandsComponent? hands)) { Owner.PopupMessage(eventArgs.User, Loc.GetString("You have no hands!")); return true; } IEntity? swap = null; if (!IdSlotEmpty) { // Swap swap = _idSlot.ContainedEntities[0]; } if (!hands.Drop(item)) { return true; } if (swap != null) { hands.PutInHand(swap.GetComponent()); } InsertIdCard(idCardComponent); UpdatePDAUserInterface(); return true; } private bool TryInsertPen(InteractUsingEventArgs eventArgs) { var item = eventArgs.Using; if (_penSlot.Contains(item)) return false; if (!eventArgs.User.TryGetComponent(out IHandsComponent? hands)) { Owner.PopupMessage(eventArgs.User, Loc.GetString("You have no hands!")); return true; } IEntity? swap = null; if (!PenSlotEmpty) { // Swap swap = _penSlot.ContainedEntities[0]; } if (!hands.Drop(item)) { return true; } if (swap != null) { hands.PutInHand(swap.GetComponent()); } // Insert Pen _penSlot.Insert(item); UpdatePDAUserInterface(); return true; } public async Task InteractUsing(InteractUsingEventArgs eventArgs) { var item = eventArgs.Using; if (item.TryGetComponent(out var idCardComponent)) { return TryInsertIdCard(eventArgs, idCardComponent); } if (item.HasComponent()) { return TryInsertPen(eventArgs); } return false; } void IActivate.Activate(ActivateEventArgs eventArgs) { if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) { return; } UserInterface?.Toggle(actor.playerSession); UpdatePDAAppearance(); } public bool UseEntity(UseEntityEventArgs eventArgs) { if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) { return false; } UserInterface?.Toggle(actor.playerSession); UpdatePDAAppearance(); return true; } public void SetPDAOwner(string name) { OwnerName = name; UpdatePDAUserInterface(); } public void InsertIdCard(IdCardComponent card) { _idSlot.Insert(card.Owner); ContainedID = card; EntitySystem.Get().PlayFromEntity("/Audio/Weapons/Guns/MagIn/batrifle_magin.ogg", Owner); } /// /// Initialize the PDA's syndicate uplink account. /// /// public void InitUplinkAccount(UplinkAccount acc) { _syndicateUplinkAccount = acc; _uplinkManager.AddNewAccount(_syndicateUplinkAccount); _syndicateUplinkAccount.BalanceChanged += account => { UpdatePDAUserInterface(); }; UpdatePDAUserInterface(); } private void ToggleLight() { if (!Owner.TryGetComponent(out PointLightComponent? light)) { return; } _lightOn = !_lightOn; light.Enabled = _lightOn; EntitySystem.Get().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner); UpdatePDAUserInterface(); } private void HandleIDEjection(IEntity pdaUser) { if (ContainedID == null) { return; } var cardEntity = ContainedID.Owner; _idSlot.Remove(cardEntity); var hands = pdaUser.GetComponent(); var cardItemComponent = cardEntity.GetComponent(); hands.PutInHandOrDrop(cardItemComponent); ContainedID = null; EntitySystem.Get().PlayFromEntity("/Audio/Machines/id_swipe.ogg", Owner); UpdatePDAUserInterface(); } private void HandlePenEjection(IEntity pdaUser) { if (PenSlotEmpty) return; var pen = _penSlot.ContainedEntities[0]; _penSlot.Remove(pen); var hands = pdaUser.GetComponent(); var itemComponent = pen.GetComponent(); hands.PutInHandOrDrop(itemComponent); UpdatePDAUserInterface(); } [Verb] public sealed class EjectIDVerb : Verb { protected override void GetData(IEntity user, PDAComponent component, VerbData data) { if (!ActionBlockerSystem.CanInteract(user)) { data.Visibility = VerbVisibility.Invisible; return; } data.Text = Loc.GetString("Eject ID"); data.Visibility = component.IdSlotEmpty ? VerbVisibility.Invisible : VerbVisibility.Visible; } protected override void Activate(IEntity user, PDAComponent component) { component.HandleIDEjection(user); } } [Verb] public sealed class EjectPenVerb : Verb { protected override void GetData(IEntity user, PDAComponent component, VerbData data) { if (!ActionBlockerSystem.CanInteract(user)) { data.Visibility = VerbVisibility.Invisible; return; } data.Text = Loc.GetString("Eject Pen"); data.Visibility = component.PenSlotEmpty ? VerbVisibility.Invisible : VerbVisibility.Visible; } protected override void Activate(IEntity user, PDAComponent component) { component.HandlePenEjection(user); } } [Verb] public sealed class ToggleFlashlightVerb : Verb { protected override void GetData(IEntity user, PDAComponent component, VerbData data) { if (!ActionBlockerSystem.CanInteract(user)) { data.Visibility = VerbVisibility.Invisible; return; } data.Text = Loc.GetString("Toggle flashlight"); } protected override void Activate(IEntity user, PDAComponent component) { component.ToggleLight(); } } private ISet? GetContainedAccess() { return ContainedID?.Owner?.GetComponent()?.Tags; } ISet IAccess.Tags => _accessSet; bool IAccess.IsReadOnly => true; void IAccess.SetTags(IEnumerable newTags) { throw new NotSupportedException("PDA access list is read-only."); } private sealed class PdaAccessSet : ISet { private readonly PDAComponent _pdaComponent; private static readonly HashSet EmptySet = new HashSet(); public PdaAccessSet(PDAComponent pdaComponent) { _pdaComponent = pdaComponent; } public IEnumerator GetEnumerator() { var contained = _pdaComponent.GetContainedAccess() ?? EmptySet; return contained.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } void ICollection.Add(string item) { throw new NotSupportedException("PDA access list is read-only."); } public void ExceptWith(IEnumerable other) { throw new NotSupportedException("PDA access list is read-only."); } public void IntersectWith(IEnumerable other) { throw new NotSupportedException("PDA access list is read-only."); } public bool IsProperSubsetOf(IEnumerable other) { var set = _pdaComponent.GetContainedAccess() ?? EmptySet; return set.IsProperSubsetOf(other); } public bool IsProperSupersetOf(IEnumerable other) { var set = _pdaComponent.GetContainedAccess() ?? EmptySet; return set.IsProperSupersetOf(other); } public bool IsSubsetOf(IEnumerable other) { var set = _pdaComponent.GetContainedAccess() ?? EmptySet; return set.IsSubsetOf(other); } public bool IsSupersetOf(IEnumerable other) { var set = _pdaComponent.GetContainedAccess() ?? EmptySet; return set.IsSupersetOf(other); } public bool Overlaps(IEnumerable other) { var set = _pdaComponent.GetContainedAccess() ?? EmptySet; return set.Overlaps(other); } public bool SetEquals(IEnumerable other) { var set = _pdaComponent.GetContainedAccess() ?? EmptySet; return set.SetEquals(other); } public void SymmetricExceptWith(IEnumerable other) { throw new NotSupportedException("PDA access list is read-only."); } public void UnionWith(IEnumerable other) { throw new NotSupportedException("PDA access list is read-only."); } bool ISet.Add(string item) { throw new NotSupportedException("PDA access list is read-only."); } public void Clear() { throw new NotSupportedException("PDA access list is read-only."); } public bool Contains(string item) { return _pdaComponent.GetContainedAccess()?.Contains(item) ?? false; } public void CopyTo(string[] array, int arrayIndex) { var set = _pdaComponent.GetContainedAccess() ?? EmptySet; set.CopyTo(array, arrayIndex); } public bool Remove(string item) { throw new NotSupportedException("PDA access list is read-only."); } public int Count => _pdaComponent.GetContainedAccess()?.Count ?? 0; public bool IsReadOnly => true; } } }