using System.Linq; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Inventory; using Content.Shared.Item; using Content.Shared.NameIdentifier; using Content.Shared.Preferences.Loadouts; using Content.Shared.Roles; using Content.Shared.Storage; using Content.Shared.Storage.EntitySystems; using Robust.Shared.Collections; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; using Content.Shared.Implants; // Frontier using Content.Shared.Implants.Components; // Frontier using Content.Shared.Radio.Components; // Frontier using Robust.Shared.Containers; // Frontier using Robust.Shared.Network; // Frontier namespace Content.Shared.Station; public abstract class SharedStationSpawningSystem : EntitySystem { [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] protected readonly InventorySystem InventorySystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; [Dependency] private readonly SharedStorageSystem _storage = default!; [Dependency] private readonly SharedTransformSystem _xformSystem = default!; [Dependency] private readonly INetManager _net = default!; // Frontier [Dependency] private readonly SharedContainerSystem _container = default!; // Frontier [Dependency] private readonly SharedImplanterSystem _implanter = default!; // Frontier private EntityQuery _handsQuery; private EntityQuery _inventoryQuery; private EntityQuery _storageQuery; private EntityQuery _xformQuery; public override void Initialize() { base.Initialize(); _handsQuery = GetEntityQuery(); _inventoryQuery = GetEntityQuery(); _storageQuery = GetEntityQuery(); _xformQuery = GetEntityQuery(); } /// /// Equips the data from a `RoleLoadout` onto an entity. /// /// /// Frontier: must run on the server, requires bank access. /// Frontier: currently not charging the player for this. /// public void EquipRoleLoadout(EntityUid entity, RoleLoadout loadout, RoleLoadoutPrototype roleProto) { // Order loadout selections by the order they appear on the prototype. foreach (var group in loadout.SelectedLoadouts.OrderBy(x => roleProto.Groups.FindIndex(e => e == x.Key))) { List> equippedItems = new(); //Frontier - track purchased items (list: few items) foreach (var items in group.Value) { if (!PrototypeManager.TryIndex(items.Prototype, out var loadoutProto)) { Log.Error($"Unable to find loadout prototype for {items.Prototype}"); continue; } EquipStartingGear(entity, loadoutProto, raiseEvent: false); equippedItems.Add(loadoutProto.ID); // Frontier } // If a character cannot afford their current job loadout, ensure they have fallback items for mandatory categories. if (PrototypeManager.TryIndex(group.Key, out var groupPrototype) && equippedItems.Count < groupPrototype.MinLimit) { foreach (var fallback in groupPrototype.Fallbacks) { // Do not duplicate items in loadout if (equippedItems.Contains(fallback)) { continue; } if (!PrototypeManager.TryIndex(fallback, out var loadoutProto)) { Log.Error($"Unable to find loadout prototype for fallback {fallback}"); continue; } EquipStartingGear(entity, loadoutProto, raiseEvent: false); equippedItems.Add(fallback); // Minimum number of items equipped, no need to load more prototypes. if (equippedItems.Count >= groupPrototype.MinLimit) break; } } // End Frontier } EquipRoleName(entity, loadout, roleProto); } /// /// Applies the role's name as applicable to the entity. /// public void EquipRoleName(EntityUid entity, RoleLoadout loadout, RoleLoadoutPrototype roleProto) { string? name = null; if (roleProto.CanCustomizeName) { name = loadout.EntityName; } if (string.IsNullOrEmpty(name) && PrototypeManager.TryIndex(roleProto.NameDataset, out var nameData)) { name = Loc.GetString(_random.Pick(nameData.Values)); } if (!string.IsNullOrEmpty(name)) { _metadata.SetEntityName(entity, name); } } public void EquipStartingGear(EntityUid entity, LoadoutPrototype loadout, bool raiseEvent = true) { EquipStartingGear(entity, loadout.StartingGear, raiseEvent); EquipStartingGear(entity, (IEquipmentLoadout)loadout, raiseEvent); } /// /// /// public void EquipStartingGear(EntityUid entity, ProtoId? startingGear, bool raiseEvent = true) { PrototypeManager.TryIndex(startingGear, out var gearProto); EquipStartingGear(entity, gearProto, raiseEvent); } /// /// /// public void EquipStartingGear(EntityUid entity, StartingGearPrototype? startingGear, bool raiseEvent = true) { EquipStartingGear(entity, (IEquipmentLoadout?)startingGear, raiseEvent); } /// /// Equips starting gear onto the given entity. /// /// Entity to load out. /// Starting gear to use. /// Should we raise the event for equipped. Set to false if you will call this manually public void EquipStartingGear(EntityUid entity, IEquipmentLoadout? startingGear, bool raiseEvent = true) { if (startingGear == null) return; var xform = _xformQuery.GetComponent(entity); if (InventorySystem.TryGetSlots(entity, out var slotDefinitions)) { foreach (var slot in slotDefinitions) { var equipmentStr = startingGear.GetGear(slot.Name); if (!string.IsNullOrEmpty(equipmentStr)) { var equipmentEntity = EntityManager.SpawnEntity(equipmentStr, xform.Coordinates); InventorySystem.TryEquip(entity, equipmentEntity, slot.Name, silent: true, force: true); } } } if (_handsQuery.TryComp(entity, out var handsComponent)) { var inhand = startingGear.Inhand; var coords = xform.Coordinates; foreach (var prototype in inhand) { var inhandEntity = EntityManager.SpawnEntity(prototype, coords); if (_handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent)) { _handsSystem.TryPickup(entity, inhandEntity, emptyHand, checkActionBlocker: false, handsComp: handsComponent); } } } if (startingGear.Storage.Count > 0) { var coords = _xformSystem.GetMapCoordinates(entity); _inventoryQuery.TryComp(entity, out var inventoryComp); foreach (var (slotName, entProtos) in startingGear.Storage) { if (entProtos == null || entProtos.Count == 0) continue; if (inventoryComp != null && InventorySystem.TryGetSlotEntity(entity, slotName, out var slotEnt, inventoryComponent: inventoryComp) && _storageQuery.TryComp(slotEnt, out var storage)) { foreach (var entProto in entProtos) { var spawnedEntity = Spawn(entProto, coords); _storage.Insert(slotEnt.Value, spawnedEntity, out _, storageComp: storage, playSound: false); } } } } // Frontier: extra fields // Implants must run on server, container initialization only runs on server, and lobby dummies don't work. if (_net.IsServer && startingGear.Implants.Count > 0) { var coords = _xformSystem.GetMapCoordinates(entity); foreach (var entProto in startingGear.Implants) { var spawnedEntity = Spawn(entProto, coords); if (TryComp(spawnedEntity, out var implanter)) _implanter.Implant(entity, entity, spawnedEntity, implanter); else DebugTools.Assert(false, $"Entity has an implant for {entProto}, which doesn't have an implanter component!"); QueueDel(spawnedEntity); } } if (startingGear.EncryptionKeys.Count > 0) { EquipEncryptionKeysIfPossible(entity, startingGear.EncryptionKeys); } // PDA cartridges must run on server, installation logic exists server-side. if (_net.IsServer && startingGear.Cartridges.Count > 0) { EquipPdaCartridgesIfPossible(entity, startingGear.Cartridges); } // End Frontier if (raiseEvent) { var ev = new StartingGearEquippedEvent(entity); RaiseLocalEvent(entity, ref ev); } } // Frontier: extra loadout fields /// Function to equip an entity with encryption keys. /// If not possible, will delete them. /// Only called in practice server-side. /// /// The entity to receive equipment. /// The encryption key prototype IDs to equip. protected void EquipEncryptionKeysIfPossible(EntityUid entity, List encryptionKeys) { if (!InventorySystem.TryGetSlotEntity(entity, "ears", out var slotEnt)) { DebugTools.Assert(false, $"Entity {entity} has a non-empty encryption key loadout, but doesn't have a headset!"); return; } if (!_container.TryGetContainer(slotEnt.Value, EncryptionKeyHolderComponent.KeyContainerName, out var keyContainer)) { DebugTools.Assert(false, $"Entity {entity} has a non-empty encryption key loadout, but their headset doesn't have an encryption key container!"); return; } var coords = _xformSystem.GetMapCoordinates(entity); foreach (var entProto in encryptionKeys) { var spawnedEntity = Spawn(entProto, coords); if (!_container.Insert(spawnedEntity, keyContainer)) { QueueDel(spawnedEntity); DebugTools.Assert(false, $"Entity {entity} could not insert their loadout encryption key {entProto} into their headset!"); } } } /// /// Function to equip an entity with PDA cartridges. /// If not possible, will delete them. /// Only called in practice server-side. /// /// The entity to receive equipment. /// The PDA cartridge prototype IDs to equip. protected abstract void EquipPdaCartridgesIfPossible(EntityUid entity, List encryptionKeys); // End Frontier: extra loadout fields }