diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index 3cf98c98aba590..6d3b201b652c78 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -454,10 +454,37 @@ public void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profil public EntityUid LoadProfileEntity(HumanoidCharacterProfile? humanoid, JobPrototype? job, bool jobClothes) { EntityUid dummyEnt; + bool isDummy = false; if (humanoid is not null) { + job ??= GetPreferredJob(humanoid); + var jobLoadout = LoadoutSystem.GetJobPrototype(job.ID); + humanoid.Loadouts.TryGetValue(jobLoadout, out var loadoutValue); + var dummy = _prototypeManager.Index(humanoid.Species).DollPrototype; + + if (loadoutValue != null) + { + foreach (var group in loadoutValue.SelectedLoadouts) + { + foreach (var items in group.Value) + { + if (!_prototypeManager.TryIndex(items.Prototype, out var loadoutProto)) + { + continue; + } + + if (loadoutProto.EntityDummy != String.Empty && loadoutProto.EntityDummy != null) + { + isDummy = true; + dummy = loadoutProto.EntityDummy; + break; + } + } + } + } + dummyEnt = EntityManager.SpawnEntity(dummy, MapCoordinates.Nullspace); } else @@ -465,11 +492,13 @@ public EntityUid LoadProfileEntity(HumanoidCharacterProfile? humanoid, JobProtot dummyEnt = EntityManager.SpawnEntity(_prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace); } - _humanoid.LoadProfile(dummyEnt, humanoid); + if (!isDummy) + { + _humanoid.LoadProfile(dummyEnt, humanoid); + } - if (humanoid != null && jobClothes) + if (humanoid != null && jobClothes && job != null) { - job ??= GetPreferredJob(humanoid); GiveDummyJobClothes(dummyEnt, humanoid, job); if (_prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(job.ID))) diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml.cs b/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml.cs index 36f0772d784e5d..5b5638c36c5549 100644 --- a/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml.cs +++ b/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml.cs @@ -20,7 +20,7 @@ public sealed partial class LoadoutContainer : BoxContainer public Button Select => SelectButton; - public LoadoutContainer(ProtoId proto, bool disabled, FormattedMessage? reason) + public LoadoutContainer(ProtoId proto, bool disabled, FormattedMessage? reason, string? dummy) { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); @@ -34,20 +34,24 @@ public LoadoutContainer(ProtoId proto, bool disabled, Formatte SelectButton.TooltipSupplier = _ => tooltip; } - if (_protoManager.TryIndex(proto, out var loadProto)) + if (dummy != String.Empty) { - var ent = _entManager.System().GetFirstOrNull(loadProto); + LoadoutSprite(dummy, _entity); + } + else if (_protoManager.TryIndex(proto, out var loadProto)) + { + LoadoutSprite(_entManager.System().GetFirstOrNull(loadProto), _entity); + } - if (ent != null) - { - _entity = _entManager.SpawnEntity(ent, MapCoordinates.Nullspace); - Sprite.SetEntity(_entity); + } - var spriteTooltip = new Tooltip(); - spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent(_entity.Value).EntityDescription)); - TooltipSupplier = _ => spriteTooltip; - } - } + private void LoadoutSprite(EntProtoId? proto, EntityUid? ent) + { + ent = _entManager.SpawnEntity(proto, MapCoordinates.Nullspace); + Sprite.SetEntity(ent); + var spriteTooltip = new Tooltip(); + spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent(ent.Value).EntityDescription)); + TooltipSupplier = _ => spriteTooltip; } protected override void Dispose(bool disposing) diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml.cs b/Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml.cs index bc7cfc7f481ea6..29415bdcbdb590 100644 --- a/Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml.cs +++ b/Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml.cs @@ -69,14 +69,21 @@ public void RefreshLoadouts(HumanoidCharacterProfile profile, RoleLoadout loadou foreach (var loadoutProto in _groupProto.Loadouts) { + string loadoutDummy = String.Empty; + if (!protoMan.TryIndex(loadoutProto, out var loadProto)) continue; + if (loadProto.EntityDummy != null) + { + loadoutDummy = loadProto.EntityDummy; + } + var matchingLoadout = selected.FirstOrDefault(e => e.Prototype == loadoutProto); var pressed = matchingLoadout != null; var enabled = loadout.IsValid(profile, session, loadoutProto, collection, out var reason); - var loadoutContainer = new LoadoutContainer(loadoutProto, !enabled, reason); + var loadoutContainer = new LoadoutContainer(loadoutProto, !enabled, reason, loadoutDummy); loadoutContainer.Select.Pressed = pressed; loadoutContainer.Text = loadoutSystem.GetName(loadProto); diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index e39a0943199d0e..c4b9a4d439a23c 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -128,8 +128,7 @@ public EntityUid SpawnPlayerMob( if (prototype?.JobEntity != null) { DebugTools.Assert(entity is null); - var jobEntity = EntityManager.SpawnEntity(prototype.JobEntity, coordinates); - MakeSentientCommand.MakeSentient(jobEntity, EntityManager); + var jobEntity = SpawnEntity(prototype.JobEntity, coordinates, job); // Make sure custom names get handled, what is gameticker control flow whoopy. if (loadout != null) @@ -137,11 +136,33 @@ public EntityUid SpawnPlayerMob( EquipRoleName(jobEntity, loadout, roleProto!); } - DoJobSpecials(job, jobEntity); - _identity.QueueIdentityUpdate(jobEntity); return jobEntity; } + profile?.Loadouts.TryGetValue(jobLoadout, out loadout); + + if (loadout != null && roleProto != null) + { + foreach (var group in loadout.SelectedLoadouts.OrderBy(x => roleProto.Groups.FindIndex(e => e == x.Key))) + { + foreach (var items in group.Value) + { + if (!_prototypeManager.TryIndex(items.Prototype, out var loadoutProto)) + { + continue; + } + + if (loadoutProto.Entity != null) + { + var newEntity = SpawnEntity(loadoutProto.Entity, coordinates, job); + EquipLoadout(newEntity, jobLoadout, loadout, roleProto, prototype, profile); + EntityManager.DeleteEntity(entity); + return newEntity; + } + } + } + } + string speciesId; if (_randomizeCharacters) { @@ -161,27 +182,25 @@ public EntityUid SpawnPlayerMob( if (!_prototypeManager.TryIndex(speciesId, out var species)) throw new ArgumentException($"Invalid species prototype was used: {speciesId}"); - entity ??= Spawn(species.Prototype, coordinates); + entity ??= SpawnEntity(species.Prototype, coordinates, job); if (_randomizeCharacters) { profile = HumanoidCharacterProfile.RandomWithSpecies(speciesId); } - if (loadout != null) + if (roleProto != null) { - EquipRoleLoadout(entity.Value, loadout, roleProto!); - } + // Set to default if not present + if (loadout == null) + { + loadout = new RoleLoadout(jobLoadout); + loadout.SetDefault(profile, _actors.GetSession(entity), _prototypeManager); + } - if (prototype?.StartingGear != null) - { - var startingGear = _prototypeManager.Index(prototype.StartingGear); - EquipStartingGear(entity.Value, startingGear, raiseEvent: false); + EquipLoadout(entity.Value, jobLoadout, loadout, roleProto, prototype, profile); } - var gearEquippedEv = new StartingGearEquippedEvent(entity.Value); - RaiseLocalEvent(entity.Value, ref gearEquippedEv); - if (profile != null) { if (prototype != null) @@ -195,11 +214,34 @@ public EntityUid SpawnPlayerMob( } } - DoJobSpecials(job, entity.Value); _identity.QueueIdentityUpdate(entity.Value); return entity.Value; } + private EntityUid SpawnEntity(string prototype, EntityCoordinates coordinates, JobComponent? job) + { + var entity = EntityManager.SpawnEntity(prototype, coordinates); + MakeSentientCommand.MakeSentient(entity, EntityManager); + DoJobSpecials(job, entity); + _identity.QueueIdentityUpdate(entity); + return entity; + } + + private void EquipLoadout(EntityUid entity, string jobLoadout, RoleLoadout loadout, RoleLoadoutPrototype roleProto, JobPrototype? prototype, HumanoidCharacterProfile? profile) + { + EquipRoleLoadout(entity, loadout, roleProto); + + if (prototype?.StartingGear != null) + { + var startingGear = _prototypeManager.Index(prototype.StartingGear); + EquipStartingGear(entity, startingGear, raiseEvent: false); + } + + var gearEquippedEv = new StartingGearEquippedEvent(entity); + RaiseLocalEvent(entity, ref gearEquippedEv); + + } + private void DoJobSpecials(JobComponent? job, EntityUid entity) { if (!_prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out JobPrototype? prototype)) diff --git a/Content.Shared/Clothing/LoadoutSystem.cs b/Content.Shared/Clothing/LoadoutSystem.cs index 2a686efd4fffc7..317d603e94a2dd 100644 --- a/Content.Shared/Clothing/LoadoutSystem.cs +++ b/Content.Shared/Clothing/LoadoutSystem.cs @@ -92,25 +92,32 @@ public string GetName(LoadoutPrototype loadout) { if (_protoMan.TryIndex(loadout.StartingGear, out var gear)) { - return GetName(gear); + return GetName(gear, loadout); } - return GetName((IEquipmentLoadout) loadout); + return GetName((IEquipmentLoadout) loadout, loadout); } /// /// Tries to get the name of a loadout. /// - public string GetName(IEquipmentLoadout? gear) + public string GetName(IEquipmentLoadout? gear, LoadoutPrototype? loadout) { if (gear == null) return string.Empty; + if (loadout != null + && loadout.EntityDummy != null + && _protoMan.TryIndex(loadout.EntityDummy, out var proto)) + { + return proto.Name; + } + var count = gear.Equipment.Count + gear.Storage.Values.Sum(o => o.Count) + gear.Inhand.Count; if (count == 1) { - if (gear.Equipment.Count == 1 && _protoMan.TryIndex(gear.Equipment.Values.First(), out var proto)) + if (gear.Equipment.Count == 1 && _protoMan.TryIndex(gear.Equipment.Values.First(), out proto)) { return proto.Name; } diff --git a/Content.Shared/Preferences/Loadouts/LoadoutPrototype.cs b/Content.Shared/Preferences/Loadouts/LoadoutPrototype.cs index a570b61d89e442..1c310f886983ca 100644 --- a/Content.Shared/Preferences/Loadouts/LoadoutPrototype.cs +++ b/Content.Shared/Preferences/Loadouts/LoadoutPrototype.cs @@ -27,6 +27,19 @@ public sealed partial class LoadoutPrototype : IPrototype, IEquipmentLoadout [DataField] public List Effects = new(); + /// + /// Overides the players entity + /// + [DataField] + public string? Entity { get; set; } + + /// + /// Meant to be used in conjunction with Entity + /// Entity to show as the players dummy in the lobby + /// + [DataField] + public string? EntityDummy { get; set; } + /// [DataField] public Dictionary Equipment { get; set; } = new(); diff --git a/Resources/Locale/en-US/preferences/loadout-groups.ftl b/Resources/Locale/en-US/preferences/loadout-groups.ftl index 79b49140923303..2c690be9fc7768 100644 --- a/Resources/Locale/en-US/preferences/loadout-groups.ftl +++ b/Resources/Locale/en-US/preferences/loadout-groups.ftl @@ -203,3 +203,5 @@ loadout-group-psychologist-jumpsuit = Psychologist jumpsuit loadout-group-boxer-jumpsuit = Boxer jumpsuit loadout-group-boxer-gloves = Boxer gloves + +loadout-group-borg-chassis = Cyborg chassis diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 15878a4017d00c..0d3816f16374e4 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -401,16 +401,14 @@ # Borgs - type: entity - id: PlayerBorgGeneric - parent: BorgChassisGeneric - suffix: Battery, Tools + id: BasePlayerBorgBattery + suffix: Battery + abstract: true components: - type: ContainerFill containers: borg_brain: - PositronicBrain - borg_module: - - BorgModuleTool - type: ItemSlots slots: cell_slot: @@ -420,19 +418,192 @@ nameSegments: [names_borg] - type: entity - id: PlayerBorgBattery - parent: BorgChassisGeneric - suffix: Battery + id: PlayerBorgGenericBattery + parent: [ BasePlayerBorgBattery, BorgChassisGeneric ] + +- type: entity + id: PlayerBorgGeneric + parent: PlayerBorgGenericBattery + suffix: Battery, Tools components: - type: ContainerFill containers: borg_brain: - - MMIFilled - - type: ItemSlots - slots: - cell_slot: - name: power-cell-slot-component-slot-name-default - startingItem: PowerCellMedium + - PositronicBrain + borg_module: + - BorgModuleTool + +- type: entity + id: DummyBorgGeneric + name: generic chassis + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: Mobs/Silicon/chassis.rsi + drawdepth: Mobs + noRot: true + layers: + - state: robot + - state: robot_e + shader: unshaded + +- type: entity + id: PlayerBorgMiningBattery + parent: [ BasePlayerBorgBattery, BorgChassisMining ] + +- type: entity + id: PlayerBorgMining + parent: PlayerBorgMiningBattery + suffix: Battery, Tools + components: + - type: ContainerFill + containers: + borg_brain: + - PositronicBrain + borg_module: + - BorgModuleTool + - BorgModuleAppraisal + +- type: entity + id: DummyBorgMining + name: mining chassis + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: Mobs/Silicon/chassis.rsi + drawdepth: Mobs + noRot: true + layers: + - state: miner + map: ["movement"] + - state: miner_e + shader: unshaded + +- type: entity + id: PlayerBorgEngineerBattery + parent: [ BasePlayerBorgBattery, BorgChassisEngineer ] + +- type: entity + id: PlayerBorgEngineer + parent: PlayerBorgEngineerBattery + suffix: Battery, Tools + components: + - type: ContainerFill + containers: + borg_brain: + - PositronicBrain + borg_module: + - BorgModuleTool + - BorgModuleConstruction + +- type: entity + id: DummyBorgEngineer + name: engineering chassis + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: Mobs/Silicon/chassis.rsi + drawdepth: Mobs + noRot: true + layers: + - state: engineer + - state: engineer_e + shader: unshaded + +- type: entity + id: PlayerBorgJanitorBattery + parent: [ BasePlayerBorgBattery, BorgChassisJanitor ] + +- type: entity + id: PlayerBorgJanitor + parent: PlayerBorgJanitorBattery + suffix: Battery, Tools + components: + - type: ContainerFill + containers: + borg_brain: + - PositronicBrain + borg_module: + - BorgModuleTool + - BorgModuleCleaning + +- type: entity + id: DummyBorgJanitor + name: janitor chassis + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: Mobs/Silicon/chassis.rsi + drawdepth: Mobs + noRot: true + layers: + - state: janitor + map: ["movement"] + - state: janitor_e + shader: unshaded + +- type: entity + id: PlayerBorgMedicalBattery + parent: [ BasePlayerBorgBattery, BorgChassisMedical ] + +- type: entity + id: PlayerBorgMedical + parent: PlayerBorgMedicalBattery + suffix: Battery, Tools + components: + - type: ContainerFill + containers: + borg_brain: + - PositronicBrain + borg_module: + - BorgModuleTool + - BorgModuleTreatment + +- type: entity + id: DummyBorgMedical + name: medical chassis + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: Mobs/Silicon/chassis.rsi + drawdepth: Mobs + noRot: true + layers: + - state: medical + map: ["movement"] + - state: medical_e + shader: unshaded + +- type: entity + id: PlayerBorgServiceBattery + parent: [ BasePlayerBorgBattery, BorgChassisService ] + +- type: entity + id: PlayerBorgService + parent: PlayerBorgServiceBattery + suffix: Battery, Tools + components: + - type: ContainerFill + containers: + borg_brain: + - PositronicBrain + borg_module: + - BorgModuleTool + - BorgModuleService + +- type: entity + id: DummyBorgService + name: service chassis + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: Mobs/Silicon/chassis.rsi + drawdepth: Mobs + noRot: true + layers: + - state: service + - state: service_e + shader: unshaded - type: entity id: PlayerBorgSyndicateAssaultBattery diff --git a/Resources/Prototypes/Loadouts/Jobs/Science/borg.yml b/Resources/Prototypes/Loadouts/Jobs/Science/borg.yml new file mode 100644 index 00000000000000..eb0604cdab00d2 --- /dev/null +++ b/Resources/Prototypes/Loadouts/Jobs/Science/borg.yml @@ -0,0 +1,29 @@ +- type: loadout + id: BorgGeneric + entity: PlayerBorgGeneric + entityDummy: DummyBorgGeneric + +- type: loadout + id: BorgMining + entity: PlayerBorgMining + entityDummy: DummyBorgMining + +- type: loadout + id: BorgEngineer + entity: PlayerBorgEngineer + entityDummy: DummyBorgEngineer + +- type: loadout + id: BorgJanitor + entity: PlayerBorgJanitor + entityDummy: DummyBorgJanitor + +- type: loadout + id: BorgMedical + entity: PlayerBorgMedical + entityDummy: DummyBorgMedical + +- type: loadout + id: BorgService + entity: PlayerBorgService + entityDummy: DummyBorgService diff --git a/Resources/Prototypes/Loadouts/loadout_groups.yml b/Resources/Prototypes/Loadouts/loadout_groups.yml index fe17ea0fffc498..9d016982232c39 100644 --- a/Resources/Prototypes/Loadouts/loadout_groups.yml +++ b/Resources/Prototypes/Loadouts/loadout_groups.yml @@ -1329,3 +1329,16 @@ hidden: true loadouts: - LoadoutSpeciesBreathToolSecurity + +- type: loadoutGroup + id: BorgChassis + name: loadout-group-borg-chassis + minLimit: 1 + maxLimit: 1 + loadouts: + - BorgGeneric + - BorgMining + - BorgEngineer + - BorgJanitor + - BorgMedical + - BorgService diff --git a/Resources/Prototypes/Loadouts/role_loadouts.yml b/Resources/Prototypes/Loadouts/role_loadouts.yml index 25b43fc22a715a..a611f6ea3b7089 100644 --- a/Resources/Prototypes/Loadouts/role_loadouts.yml +++ b/Resources/Prototypes/Loadouts/role_loadouts.yml @@ -519,6 +519,11 @@ - Trinkets - GroupSpeciesBreathTool +- type: roleLoadout + id: JobBorg + groups: + - BorgChassis + # These loadouts are used for non-crew spawns, like off-station antags and event mobs # They will be used without player configuration, thus they will only ever apply what is forced by MinLimit diff --git a/Resources/Prototypes/Roles/Jobs/Science/borg.yml b/Resources/Prototypes/Roles/Jobs/Science/borg.yml index e35270d57dc9cf..f112cfc8c39446 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/borg.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/borg.yml @@ -25,5 +25,4 @@ canBeAntag: false icon: JobIconBorg supervisors: job-supervisors-rd - jobEntity: PlayerBorgGeneric applyTraits: false