using System.Linq; using Content.Server.PDA.Ringer; using Content.Server.Store.Systems; using Content.Server.StoreDiscount.Systems; using Content.Shared.FixedPoint; using Content.Shared.Hands.EntitySystems; using Content.Shared.Implants; using Content.Shared.Inventory; using Content.Shared.Mind; using Content.Shared.PDA; using Content.Shared.Store; using Content.Shared.Store.Components; using Robust.Shared.Map; using Robust.Shared.Prototypes; namespace Content.Server.Traitor.Uplink; public sealed partial class UplinkSystem : EntitySystem { [Dependency] private InventorySystem _inventorySystem = default!; [Dependency] private SharedHandsSystem _handsSystem = default!; [Dependency] private StoreSystem _store = default!; [Dependency] private SharedSubdermalImplantSystem _subdermalImplant = default!; [Dependency] private SharedMindSystem _mind = default!; [Dependency] private RingerSystem _ringer = default!; public static readonly EntProtoId TraitorUplinkStore = "StorePresetRemoteUplink"; public static readonly ProtoId TelecrystalCurrencyPrototype = "Telecrystal"; private static readonly EntProtoId FallbackUplinkImplant = "UplinkImplant"; private static readonly ProtoId FallbackUplinkCatalog = "UplinkUplinkImplanter"; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnRemoteStoreImplanted); } private void OnRemoteStoreImplanted(Entity entity, ref ImplantImplantedEvent args) { if (_mind.GetMind(args.Implanted) is not { } mind ) return; var storeEnumerator = EntityQueryEnumerator(); while (storeEnumerator.MoveNext(out var uid, out _, out var store)) { if (store.AccountOwner != mind) continue; entity.Comp.Store = uid; return; } // If we didn't have an uplink, make an empty one. entity.Comp.Store = Spawn(TraitorUplinkStore, MapCoordinates.Nullspace); SetUplink(args.Implanted, entity.Comp.Store.Value, 0, false); Log.Error($"{ToPrettyString(args.Implanted)} did not have an uplink when they were implanted."); } /// /// Adds an uplink to the target /// /// The person who is getting the uplink /// The amount of currency on the uplink. If null, will just use the amount specified in the preset. /// The code which was generated, if any. /// The entity that will actually have the uplink functionality. Defaults to the PDA if null. /// Marker that enables discounts for uplink items. /// Binds the uplink to the specific uplink entity. /// Whether the uplink was added successfully to a PDA, implant or not at all. public AddUplinkResult AddUplink( EntityUid user, FixedPoint2 balance, out Note[]? code, EntityUid? uplinkEntity = null, bool giveDiscounts = false, bool bindToPda = false) { code = null; var storeEntity = Spawn(TraitorUplinkStore, MapCoordinates.Nullspace); if (TryAddEntityUplink(user, balance, out var generatedCode, uplinkEntity, storeEntity, giveDiscounts, bindToPda)) { code = generatedCode; return AddUplinkResult.Pda; } if (TryImplantUplink(user, storeEntity, balance, giveDiscounts)) { return AddUplinkResult.Implant; } Del(storeEntity); return AddUplinkResult.Failure; } public bool TryAddEntityUplink( EntityUid user, FixedPoint2 balance, out Note[]? code, EntityUid? uplinkEntity, EntityUid storeEntity, bool giveDiscounts = false, bool bindToPda = false) { code = null; uplinkEntity ??= FindUplinkTarget(user); if (uplinkEntity == null) return false; var ev = new GenerateUplinkCodeEvent(); RaiseLocalEvent(storeEntity, ref ev); if (ev.Code == null) { QueueDel(storeEntity); return false; } code = ev.Code; if (bindToPda) { var accessComp = EnsureComp(storeEntity); _ringer.SetBoundUplinkEntity((storeEntity, accessComp), uplinkEntity.Value); } SetUplink(user, storeEntity, balance, giveDiscounts); return true; } /// /// Configure TC for the uplink /// private void SetUplink(EntityUid user, EntityUid store, FixedPoint2 balance, bool giveDiscounts) { if (!_mind.TryGetMind(user, out var mind, out _)) return; var storeComp = EnsureComp(store); storeComp.AccountOwner = mind; storeComp.Balance.Clear(); _store.TryAddCurrency(new() { { TelecrystalCurrencyPrototype, balance } }, store, storeComp); var uplinkInitializedEvent = new StoreInitializedEvent( TargetUser: mind, Store: store, UseDiscounts: giveDiscounts, Listings: _store.GetAvailableListings(mind, store, storeComp) .ToArray()); RaiseLocalEvent(ref uplinkInitializedEvent); } /// /// Implant an uplink as a fallback measure if the traitor had no PDA /// public bool TryImplantUplink(EntityUid user, EntityUid storeEntity, FixedPoint2 balance, bool giveDiscounts) { if (!ProtoMan.Resolve(FallbackUplinkCatalog, out var catalog)) return false; if (!catalog.Cost.TryGetValue(TelecrystalCurrencyPrototype, out var cost)) return false; if (balance < cost) // Can't use Math functions on FixedPoint2 balance = 0; else balance = balance - cost; SetUplink(user, storeEntity, balance, giveDiscounts); var implant = _subdermalImplant.AddImplant(user, FallbackUplinkImplant); if (!HasComp(implant)) { Log.Error($"Implant does not have the store component {implant}"); return false; } return true; } /// /// Finds the entity that can hold an uplink for a user. /// Usually this is a pda in their pda slot, but can also be in their hands. (but not pockets or inside bag, etc.) /// public EntityUid? FindUplinkTarget(EntityUid user) { // Try to find PDA in inventory if (_inventorySystem.TryGetContainerSlotEnumerator(user, out var containerSlotEnumerator)) { while (containerSlotEnumerator.MoveNext(out var containerSlot)) { var pdaUid = containerSlot.ContainedEntity; if (HasComp(pdaUid) && HasComp(pdaUid)) return pdaUid.Value; } } // Also check hands foreach (var item in _handsSystem.EnumerateHeld(user)) { if (HasComp(item) && HasComp(item)) return item; } return null; } } public enum AddUplinkResult { Pda, Implant, Failure, }