]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Borg type switching. (#32586)
authorPieter-Jan Briers <pieterjan.briers+git@gmail.com>
Thu, 14 Nov 2024 17:08:35 +0000 (18:08 +0100)
committerGitHub <noreply@github.com>
Thu, 14 Nov 2024 17:08:35 +0000 (11:08 -0600)
* Borg type switching.

This allows borgs (new spawn or constructed) to select their chassis type on creation, like in SS13. This removes the need for the many different chassis types, and means round-start borgs can actually play the game immediately instead of waiting for science to unlock everything.

New borgs have an additional action that allows them to select their type. This opens a nice window with basic information about the borgs and a select button. Once a type has been selected it is permanent for that borg chassis.

These borg types also immediately start the borg with specific modules, so they do not need to be printed. Additional modules can still be inserted for upgrades, though this is now less critical. The built-in modules cannot be removed, but are shown in the UI.

The modules that each borg type starts with:

* Generic: tools
* Engineering: advanced tools, construction, RCD, cable
* Salvage: Grappling gun, appraisal, mining
* Janitor: cleaning, light replacer
* Medical: treatment
* Service: music, service, clowning

Specialized borgs have 3 additional module slots available on top of the ones listed above, generic borgs have 5.

Borg types are specified in a new BorgTypePrototype. These prototypes specify all information about the borg type. It is assigned to the borg entity through a mix of client side, server, and shared code. Some of the involved components were made networked, others are just ensured they're set on both sides of the wire.

The most gnarly change is the inventory template prototype, which needs to change purely to modify the borg hat offset. I managed to bodge this in with an API that *probably* won't explode for specifically for this use case, but it's still not the most clean of API designs.

Parts for specific borg chassis have been removed (so much deleted YAML) and specialized borg modules that are in the base set of a type have been removed from the exosuit fab as there's no point to printing those.

The ability to "downgrade" a borg so it can select a new chassis, like in SS13, is something that would be nice, but was not high enough priority for me to block the feature on. I did keep it in mind with some of the code, so it may be possible in the future.

There is no fancy animation when selecting borg types like in SS13, because I didn't think it was high priority, and it would add a lot of complex code.

* Fix sandbox failure due to collection expression.

* Module tweak

Fix salvage borg modules still having research/lathe recipes

Engie borg has regular tool module, not advanced.

* Fix inventory system breakage

* Fix migrations

Some things were missing

* Guidebook rewordings & review

* MinWidth on confirm selection button

49 files changed:
Content.Client/Clothing/ClientClothingSystem.cs
Content.Client/Inventory/ClientInventorySystem.cs
Content.Client/Overlays/EquipmentHudSystem.cs
Content.Client/Overlays/ShowHealthBarsSystem.cs
Content.Client/Overlays/ShowHealthIconsSystem.cs
Content.Client/Silicons/Borgs/BorgMenu.xaml.cs
Content.Client/Silicons/Borgs/BorgModuleControl.xaml.cs
Content.Client/Silicons/Borgs/BorgSelectTypeMenu.xaml [new file with mode: 0644]
Content.Client/Silicons/Borgs/BorgSelectTypeMenu.xaml.cs [new file with mode: 0644]
Content.Client/Silicons/Borgs/BorgSelectTypeUserInterface.cs [new file with mode: 0644]
Content.Client/Silicons/Borgs/BorgSwitchableTypeSystem.cs [new file with mode: 0644]
Content.Client/Silicons/Borgs/BorgSystem.cs
Content.Server/Silicons/Borgs/BorgSwitchableTypeSystem.cs [new file with mode: 0644]
Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs
Content.Server/Silicons/Borgs/BorgSystem.Ui.cs
Content.Server/Silicons/Borgs/BorgSystem.cs
Content.Shared/Interaction/InteractionPopupSystem.cs
Content.Shared/Inventory/InventoryComponent.cs
Content.Shared/Inventory/InventorySystem.Slots.cs
Content.Shared/Overlays/ShowHealthBarsComponent.cs
Content.Shared/Overlays/ShowHealthIconsComponent.cs
Content.Shared/Silicons/Borgs/BorgTypePrototype.cs [new file with mode: 0644]
Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs
Content.Shared/Silicons/Borgs/Components/BorgModuleComponent.cs
Content.Shared/Silicons/Borgs/Components/BorgSwitchableTypeComponent.cs [new file with mode: 0644]
Content.Shared/Silicons/Borgs/SharedBorgSwitchableTypeSystem.cs [new file with mode: 0644]
Content.Shared/Silicons/Borgs/SharedBorgSystem.cs
Resources/Locale/en-US/borg/borg.ftl
Resources/Prototypes/Actions/borgs.yml
Resources/Prototypes/Body/Parts/silicon.yml
Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml
Resources/Prototypes/Entities/Mobs/Player/silicon.yml
Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_parts.yml [deleted file]
Resources/Prototypes/Entities/Objects/Specific/Robotics/endoskeleton.yml
Resources/Prototypes/Entities/Structures/Machines/lathe.yml
Resources/Prototypes/InventoryTemplates/borg.yml
Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml
Resources/Prototypes/Recipes/Lathes/robotics.yml
Resources/Prototypes/Research/civilianservices.yml
Resources/Prototypes/Research/industrial.yml
Resources/Prototypes/Roles/Jobs/Science/borg.yml
Resources/Prototypes/borg_types.yml [new file with mode: 0644]
Resources/Prototypes/tags.yml
Resources/ServerInfo/Guidebook/Science/Cyborgs.xml
Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json
Resources/Textures/Interface/Actions/actions_borg.rsi/select-type.png [new file with mode: 0644]
Resources/migration.yml

index 3462fc923603678ccdb1c90c7d5916c299fc4b87..46f879e815608147c7f55b8d083184d7bc4102f3 100644 (file)
@@ -58,6 +58,7 @@ public sealed class ClientClothingSystem : ClothingSystem
         base.Initialize();
 
         SubscribeLocalEvent<ClothingComponent, GetEquipmentVisualsEvent>(OnGetVisuals);
+        SubscribeLocalEvent<ClothingComponent, InventoryTemplateUpdated>(OnInventoryTemplateUpdated);
 
         SubscribeLocalEvent<InventoryComponent, VisualsChangedEvent>(OnVisualsChanged);
         SubscribeLocalEvent<SpriteComponent, DidUnequipEvent>(OnDidUnequip);
@@ -70,11 +71,7 @@ public sealed class ClientClothingSystem : ClothingSystem
         if (args.Sprite == null)
             return;
 
-        var enumerator = _inventorySystem.GetSlotEnumerator((uid, component));
-        while (enumerator.NextItem(out var item, out var slot))
-        {
-            RenderEquipment(uid, item, slot.Name, component);
-        }
+        UpdateAllSlots(uid, component);
 
         // No clothing equipped -> make sure the layer is hidden, though this should already be handled by on-unequip.
         if (args.Sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer))
@@ -84,6 +81,23 @@ public sealed class ClientClothingSystem : ClothingSystem
         }
     }
 
+    private void OnInventoryTemplateUpdated(Entity<ClothingComponent> ent, ref InventoryTemplateUpdated args)
+    {
+        UpdateAllSlots(ent.Owner, clothing: ent.Comp);
+    }
+
+    private void UpdateAllSlots(
+        EntityUid uid,
+        InventoryComponent? inventoryComponent = null,
+        ClothingComponent? clothing = null)
+    {
+        var enumerator = _inventorySystem.GetSlotEnumerator((uid, inventoryComponent));
+        while (enumerator.NextItem(out var item, out var slot))
+        {
+            RenderEquipment(uid, item, slot.Name, inventoryComponent, clothingComponent: clothing);
+        }
+    }
+
     private void OnGetVisuals(EntityUid uid, ClothingComponent item, GetEquipmentVisualsEvent args)
     {
         if (!TryComp(args.Equipee, out InventoryComponent? inventory))
index 87cea4e3d2fce4a238de2b382386ef80655370db..dce401eefda54e62fba516d0a434c8ff29ea57a6 100644 (file)
@@ -235,9 +235,23 @@ namespace Content.Client.Inventory
             EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(GetNetEntity(item.Value), altInteract: true));
         }
 
+        protected override void UpdateInventoryTemplate(Entity<InventoryComponent> ent)
+        {
+            base.UpdateInventoryTemplate(ent);
+
+            if (TryComp(ent, out InventorySlotsComponent? inventorySlots))
+            {
+                foreach (var slot in ent.Comp.Slots)
+                {
+                    if (inventorySlots.SlotData.TryGetValue(slot.Name, out var slotData))
+                        slotData.SlotDef = slot;
+                }
+            }
+        }
+
         public sealed class SlotData
         {
-            public readonly SlotDefinition SlotDef;
+            public SlotDefinition SlotDef;
             public EntityUid? HeldEntity => Container?.ContainedEntity;
             public bool Blocked;
             public bool Highlighted;
index c7578b6793f5e90e115e1e89d12e051490d9dafd..502a1f36274bd0dfb35fc980510dc673a975a698 100644 (file)
@@ -14,6 +14,7 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
 {
     [Dependency] private readonly IPlayerManager _player = default!;
 
+    [ViewVariables]
     protected bool IsActive;
     protected virtual SlotFlags TargetSlots => ~SlotFlags.POCKET;
 
@@ -102,7 +103,7 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
         args.Components.Add(component);
     }
 
-    private void RefreshOverlay(EntityUid uid)
+    protected void RefreshOverlay(EntityUid uid)
     {
         if (uid != _player.LocalSession?.AttachedEntity)
             return;
index 1eb712a8988f3e53945f30e84590703b0f74e74b..b23209ff202bab6fa6bc626f1cb0289369d3850b 100644 (file)
@@ -21,9 +21,16 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
     {
         base.Initialize();
 
+        SubscribeLocalEvent<ShowHealthBarsComponent, AfterAutoHandleStateEvent>(OnHandleState);
+
         _overlay = new(EntityManager, _prototype);
     }
 
+    private void OnHandleState(Entity<ShowHealthBarsComponent> ent, ref AfterAutoHandleStateEvent args)
+    {
+        RefreshOverlay(ent);
+    }
+
     protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component)
     {
         base.UpdateInternal(component);
index 8c22c78f17cd2eefc1696756154f082979acbb36..b4d845e4217ae66ac3fc7a8da641f36f75a3169a 100644 (file)
@@ -17,6 +17,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
 {
     [Dependency] private readonly IPrototypeManager _prototypeMan = default!;
 
+    [ViewVariables]
     public HashSet<string> DamageContainers = new();
 
     public override void Initialize()
@@ -24,6 +25,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
         base.Initialize();
 
         SubscribeLocalEvent<DamageableComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
+        SubscribeLocalEvent<ShowHealthIconsComponent, AfterAutoHandleStateEvent>(OnHandleState);
     }
 
     protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthIconsComponent> component)
@@ -43,6 +45,11 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
         DamageContainers.Clear();
     }
 
+    private void OnHandleState(Entity<ShowHealthIconsComponent> ent, ref AfterAutoHandleStateEvent args)
+    {
+        RefreshOverlay(ent);
+    }
+
     private void OnGetStatusIconsEvent(Entity<DamageableComponent> entity, ref GetStatusIconsEvent args)
     {
         if (!IsActive)
index f6a861aa057b96c4842218448de46f75180de852..b8f0e69c022f868a4e3c53b9a53618682f13f6ba 100644 (file)
@@ -131,7 +131,8 @@ public sealed partial class BorgMenu : FancyWindow
         _modules.Clear();
         foreach (var module in chassis.ModuleContainer.ContainedEntities)
         {
-            var control = new BorgModuleControl(module, _entity);
+            var moduleComponent = _entity.GetComponent<BorgModuleComponent>(module);
+            var control = new BorgModuleControl(module, _entity, !moduleComponent.DefaultModule);
             control.RemoveButtonPressed += () =>
             {
                 RemoveModuleButtonPressed?.Invoke(module);
index d5cf05ba63ed8676cf4be840fe08467d4fb9405f..245425524ca095b5c054d43a5e31b9bbfa0863e8 100644 (file)
@@ -9,7 +9,7 @@ public sealed partial class BorgModuleControl : PanelContainer
 {
     public Action? RemoveButtonPressed;
 
-    public BorgModuleControl(EntityUid entity, IEntityManager entityManager)
+    public BorgModuleControl(EntityUid entity, IEntityManager entityManager, bool canRemove)
     {
         RobustXamlLoader.Load(this);
 
@@ -20,6 +20,7 @@ public sealed partial class BorgModuleControl : PanelContainer
         {
             RemoveButtonPressed?.Invoke();
         };
+        RemoveButton.Visible = canRemove;
     }
 }
 
diff --git a/Content.Client/Silicons/Borgs/BorgSelectTypeMenu.xaml b/Content.Client/Silicons/Borgs/BorgSelectTypeMenu.xaml
new file mode 100644 (file)
index 0000000..f51c2f5
--- /dev/null
@@ -0,0 +1,43 @@
+<controls:FancyWindow xmlns="https://spacestation14.io"
+                      xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+                      xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
+                      Title="{Loc 'borg-select-type-menu-title'}"
+                      SetSize="550 300">
+    <BoxContainer Orientation="Vertical">
+        <BoxContainer Orientation="Horizontal" VerticalExpand="True">
+            <!-- Left pane: selection of borg type -->
+            <BoxContainer Orientation="Vertical" MinWidth="200" Margin="2 0">
+                <Label Text="{Loc 'borg-select-type-menu-available'}" StyleClasses="LabelHeading" />
+                <ScrollContainer HScrollEnabled="False" VerticalExpand="True">
+                    <BoxContainer Name="SelectionsContainer" Orientation="Vertical" />
+                </ScrollContainer>
+            </BoxContainer>
+
+            <customControls:VSeparator />
+
+            <!-- Right pane: information about selected borg module, confirm button. -->
+            <BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="2 0">
+                <Label Text="{Loc 'borg-select-type-menu-information'}" StyleClasses="LabelHeading" />
+                <Control VerticalExpand="True">
+                    <controls:Placeholder Name="InfoPlaceholder" PlaceholderText="{Loc 'borg-select-type-menu-select-type'}" />
+                    <BoxContainer Name="InfoContents" Orientation="Vertical" Visible="False">
+                        <BoxContainer Orientation="Horizontal" Margin="0 0 0 4">
+                            <EntityPrototypeView Name="ChassisView" Scale="2,2" />
+                            <Label Name="NameLabel" HorizontalExpand="True" />
+                        </BoxContainer>
+
+                        <RichTextLabel Name="DescriptionLabel" VerticalExpand="True" VerticalAlignment="Top" />
+                    </BoxContainer>
+                </Control>
+                <controls:ConfirmButton Name="ConfirmTypeButton" Text="{Loc 'borg-select-type-menu-confirm'}"
+                                        Disabled="True" HorizontalAlignment="Right"
+                                        MinWidth="200" />
+            </BoxContainer>
+        </BoxContainer>
+
+        <controls:StripeBack Margin="0 0 0 4">
+            <Label Text="{Loc 'borg-select-type-menu-bottom-text'}" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 4 0 4"/>
+        </controls:StripeBack>
+    </BoxContainer>
+
+</controls:FancyWindow>
diff --git a/Content.Client/Silicons/Borgs/BorgSelectTypeMenu.xaml.cs b/Content.Client/Silicons/Borgs/BorgSelectTypeMenu.xaml.cs
new file mode 100644 (file)
index 0000000..e1fbd37
--- /dev/null
@@ -0,0 +1,81 @@
+using System.Linq;
+using Content.Client.UserInterface.Controls;
+using Content.Client.UserInterface.Systems.Guidebook;
+using Content.Shared.Guidebook;
+using Content.Shared.Silicons.Borgs;
+using Content.Shared.Silicons.Borgs.Components;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Silicons.Borgs;
+
+/// <summary>
+/// Menu used by borgs to select their type.
+/// </summary>
+/// <seealso cref="BorgSelectTypeUserInterface"/>
+/// <seealso cref="BorgSwitchableTypeComponent"/>
+[GenerateTypedNameReferences]
+public sealed partial class BorgSelectTypeMenu : FancyWindow
+{
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+    private BorgTypePrototype? _selectedBorgType;
+
+    public event Action<ProtoId<BorgTypePrototype>>? ConfirmedBorgType;
+
+    [ValidatePrototypeId<GuideEntryPrototype>]
+    private static readonly List<ProtoId<GuideEntryPrototype>> GuidebookEntries = new() { "Cyborgs", "Robotics" };
+
+    public BorgSelectTypeMenu()
+    {
+        RobustXamlLoader.Load(this);
+        IoCManager.InjectDependencies(this);
+
+        var group = new ButtonGroup();
+        foreach (var borgType in _prototypeManager.EnumeratePrototypes<BorgTypePrototype>().OrderBy(PrototypeName))
+        {
+            var button = new Button
+            {
+                Text = PrototypeName(borgType),
+                Group = group,
+            };
+            button.OnPressed += _ =>
+            {
+                _selectedBorgType = borgType;
+                UpdateInformation(borgType);
+            };
+            SelectionsContainer.AddChild(button);
+        }
+
+        ConfirmTypeButton.OnPressed += ConfirmButtonPressed;
+        HelpGuidebookIds = GuidebookEntries;
+    }
+
+    private void UpdateInformation(BorgTypePrototype prototype)
+    {
+        _selectedBorgType = prototype;
+
+        InfoContents.Visible = true;
+        InfoPlaceholder.Visible = false;
+        ConfirmTypeButton.Disabled = false;
+
+        NameLabel.Text = PrototypeName(prototype);
+        DescriptionLabel.Text = Loc.GetString($"borg-type-{prototype.ID}-desc");
+        ChassisView.SetPrototype(prototype.DummyPrototype);
+    }
+
+    private void ConfirmButtonPressed(BaseButton.ButtonEventArgs obj)
+    {
+        if (_selectedBorgType == null)
+            return;
+
+        ConfirmedBorgType?.Invoke(_selectedBorgType);
+    }
+
+    private static string PrototypeName(BorgTypePrototype prototype)
+    {
+        return Loc.GetString($"borg-type-{prototype.ID}-name");
+    }
+}
diff --git a/Content.Client/Silicons/Borgs/BorgSelectTypeUserInterface.cs b/Content.Client/Silicons/Borgs/BorgSelectTypeUserInterface.cs
new file mode 100644 (file)
index 0000000..8c76fad
--- /dev/null
@@ -0,0 +1,30 @@
+using Content.Shared.Silicons.Borgs.Components;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Silicons.Borgs;
+
+/// <summary>
+/// User interface used by borgs to select their type.
+/// </summary>
+/// <seealso cref="BorgSelectTypeMenu"/>
+/// <seealso cref="BorgSwitchableTypeComponent"/>
+/// <seealso cref="BorgSwitchableTypeUiKey"/>
+[UsedImplicitly]
+public sealed class BorgSelectTypeUserInterface : BoundUserInterface
+{
+    [ViewVariables]
+    private BorgSelectTypeMenu? _menu;
+
+    public BorgSelectTypeUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+    {
+    }
+
+    protected override void Open()
+    {
+        base.Open();
+
+        _menu = this.CreateWindow<BorgSelectTypeMenu>();
+        _menu.ConfirmedBorgType += prototype => SendMessage(new BorgSelectTypeMessage(prototype));
+    }
+}
diff --git a/Content.Client/Silicons/Borgs/BorgSwitchableTypeSystem.cs b/Content.Client/Silicons/Borgs/BorgSwitchableTypeSystem.cs
new file mode 100644 (file)
index 0000000..346dc5c
--- /dev/null
@@ -0,0 +1,81 @@
+using Content.Shared.Movement.Components;
+using Content.Shared.Silicons.Borgs;
+using Content.Shared.Silicons.Borgs.Components;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Silicons.Borgs;
+
+/// <summary>
+/// Client side logic for borg type switching. Sets up primarily client-side visual information.
+/// </summary>
+/// <seealso cref="SharedBorgSwitchableTypeSystem"/>
+/// <seealso cref="BorgSwitchableTypeComponent"/>
+public sealed class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeSystem
+{
+    [Dependency] private readonly BorgSystem _borgSystem = default!;
+    [Dependency] private readonly AppearanceSystem _appearance = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<BorgSwitchableTypeComponent, AfterAutoHandleStateEvent>(AfterStateHandler);
+        SubscribeLocalEvent<BorgSwitchableTypeComponent, ComponentStartup>(OnComponentStartup);
+    }
+
+    private void OnComponentStartup(Entity<BorgSwitchableTypeComponent> ent, ref ComponentStartup args)
+    {
+        UpdateEntityAppearance(ent);
+    }
+
+    private void AfterStateHandler(Entity<BorgSwitchableTypeComponent> ent, ref AfterAutoHandleStateEvent args)
+    {
+        UpdateEntityAppearance(ent);
+    }
+
+    protected override void UpdateEntityAppearance(
+        Entity<BorgSwitchableTypeComponent> entity,
+        BorgTypePrototype prototype)
+    {
+        if (TryComp(entity, out SpriteComponent? sprite))
+        {
+            sprite.LayerSetState(BorgVisualLayers.Body, prototype.SpriteBodyState);
+            sprite.LayerSetState(BorgVisualLayers.LightStatus, prototype.SpriteToggleLightState);
+        }
+
+        if (TryComp(entity, out BorgChassisComponent? chassis))
+        {
+            _borgSystem.SetMindStates(
+                (entity.Owner, chassis),
+                prototype.SpriteHasMindState,
+                prototype.SpriteNoMindState);
+
+            if (TryComp(entity, out AppearanceComponent? appearance))
+            {
+                // Queue update so state changes apply.
+                _appearance.QueueUpdate(entity, appearance);
+            }
+        }
+
+        if (prototype.SpriteBodyMovementState is { } movementState)
+        {
+            var spriteMovement = EnsureComp<SpriteMovementComponent>(entity);
+            spriteMovement.NoMovementLayers.Clear();
+            spriteMovement.NoMovementLayers["movement"] = new PrototypeLayerData
+            {
+                State = prototype.SpriteBodyState,
+            };
+            spriteMovement.MovementLayers.Clear();
+            spriteMovement.MovementLayers["movement"] = new PrototypeLayerData
+            {
+                State = movementState,
+            };
+        }
+        else
+        {
+            RemComp<SpriteMovementComponent>(entity);
+        }
+
+        base.UpdateEntityAppearance(entity, prototype);
+    }
+}
index e92ce5cc777417417faa49b8fab2a20b9ed2a120..387a56384e9a3ee4bfc6f7472fd669078ae1d075 100644 (file)
@@ -92,4 +92,18 @@ public sealed class BorgSystem : SharedBorgSystem
             sprite.LayerSetState(MMIVisualLayers.Base, state);
         }
     }
+
+    /// <summary>
+    /// Sets the sprite states used for the borg "is there a mind or not" indication.
+    /// </summary>
+    /// <param name="borg">The entity and component to modify.</param>
+    /// <param name="hasMindState">The state to use if the borg has a mind.</param>
+    /// <param name="noMindState">The state to use if the borg has no mind.</param>
+    /// <seealso cref="BorgChassisComponent.HasMindState"/>
+    /// <seealso cref="BorgChassisComponent.NoMindState"/>
+    public void SetMindStates(Entity<BorgChassisComponent> borg, string hasMindState, string noMindState)
+    {
+        borg.Comp.HasMindState = hasMindState;
+        borg.Comp.NoMindState = noMindState;
+    }
 }
diff --git a/Content.Server/Silicons/Borgs/BorgSwitchableTypeSystem.cs b/Content.Server/Silicons/Borgs/BorgSwitchableTypeSystem.cs
new file mode 100644 (file)
index 0000000..d1a32a6
--- /dev/null
@@ -0,0 +1,82 @@
+using Content.Server.Inventory;
+using Content.Server.Radio.Components;
+using Content.Shared.Inventory;
+using Content.Shared.Silicons.Borgs;
+using Content.Shared.Silicons.Borgs.Components;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Silicons.Borgs;
+
+/// <summary>
+/// Server-side logic for borg type switching. Handles more heavyweight and server-specific switching logic.
+/// </summary>
+public sealed class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeSystem
+{
+    [Dependency] private readonly BorgSystem _borgSystem = default!;
+    [Dependency] private readonly ServerInventorySystem _inventorySystem = default!;
+
+    protected override void SelectBorgModule(Entity<BorgSwitchableTypeComponent> ent, ProtoId<BorgTypePrototype> borgType)
+    {
+        var prototype = Prototypes.Index(borgType);
+
+        // Assign radio channels
+        string[] radioChannels = [.. ent.Comp.InherentRadioChannels, .. prototype.RadioChannels];
+        if (TryComp(ent, out IntrinsicRadioTransmitterComponent? transmitter))
+            transmitter.Channels = [.. radioChannels];
+
+        if (TryComp(ent, out ActiveRadioComponent? activeRadio))
+            activeRadio.Channels = [.. radioChannels];
+
+        // Borg transponder for the robotics console
+        if (TryComp(ent, out BorgTransponderComponent? transponder))
+        {
+            _borgSystem.SetTransponderSprite(
+                (ent.Owner, transponder),
+                new SpriteSpecifier.Rsi(new ResPath("Mobs/Silicon/chassis.rsi"), prototype.SpriteBodyState));
+
+            _borgSystem.SetTransponderName(
+                (ent.Owner, transponder),
+                Loc.GetString($"borg-type-{borgType}-transponder"));
+        }
+
+        // Configure modules
+        if (TryComp(ent, out BorgChassisComponent? chassis))
+        {
+            var chassisEnt = (ent.Owner, chassis);
+            _borgSystem.SetMaxModules(
+                chassisEnt,
+                prototype.ExtraModuleCount + prototype.DefaultModules.Length);
+
+            _borgSystem.SetModuleWhitelist(chassisEnt, prototype.ModuleWhitelist);
+
+            foreach (var module in prototype.DefaultModules)
+            {
+                var moduleEntity = Spawn(module);
+                var borgModule = Comp<BorgModuleComponent>(moduleEntity);
+                _borgSystem.SetBorgModuleDefault((moduleEntity, borgModule), true);
+                _borgSystem.InsertModule(chassisEnt, moduleEntity);
+            }
+        }
+
+        // Configure special components
+        if (Prototypes.TryIndex(ent.Comp.SelectedBorgType, out var previousPrototype))
+        {
+            if (previousPrototype.AddComponents is { } removeComponents)
+                EntityManager.RemoveComponents(ent, removeComponents);
+        }
+
+        if (prototype.AddComponents is { } addComponents)
+        {
+            EntityManager.AddComponents(ent, addComponents);
+        }
+
+        // Configure inventory template (used for hat spacing)
+        if (TryComp(ent, out InventoryComponent? inventory))
+        {
+            _inventorySystem.SetTemplateId((ent.Owner, inventory), prototype.InventoryTemplateId);
+        }
+
+        base.SelectBorgModule(ent, borgType);
+    }
+}
index d5a429db0309f33c184a09390019139ecca3d77e..f95a5807360ca656b1a059acc5fe510360a5d16a 100644 (file)
@@ -2,6 +2,7 @@ using System.Linq;
 using Content.Shared.Hands.Components;
 using Content.Shared.Interaction.Components;
 using Content.Shared.Silicons.Borgs.Components;
+using Content.Shared.Whitelist;
 using Robust.Shared.Containers;
 
 namespace Content.Server.Silicons.Borgs;
@@ -300,6 +301,24 @@ public sealed partial class BorgSystem
         return true;
     }
 
+    /// <summary>
+    /// Check if a module can be removed from a borg.
+    /// </summary>
+    /// <param name="borg">The borg that the module is being removed from.</param>
+    /// <param name="module">The module to remove from the borg.</param>
+    /// <param name="user">The user attempting to remove the module.</param>
+    /// <returns>True if the module can be removed.</returns>
+    public bool CanRemoveModule(
+        Entity<BorgChassisComponent> borg,
+        Entity<BorgModuleComponent> module,
+        EntityUid? user = null)
+    {
+        if (module.Comp.DefaultModule)
+            return false;
+
+        return true;
+    }
+
     /// <summary>
     /// Installs and activates all modules currently inside the borg's module container
     /// </summary>
@@ -369,4 +388,24 @@ public sealed partial class BorgSystem
         var ev = new BorgModuleUninstalledEvent(uid);
         RaiseLocalEvent(module, ref ev);
     }
+
+    /// <summary>
+    /// Sets <see cref="BorgChassisComponent.MaxModules"/>.
+    /// </summary>
+    /// <param name="ent">The borg to modify.</param>
+    /// <param name="maxModules">The new max module count.</param>
+    public void SetMaxModules(Entity<BorgChassisComponent> ent, int maxModules)
+    {
+        ent.Comp.MaxModules = maxModules;
+    }
+
+    /// <summary>
+    /// Sets <see cref="BorgChassisComponent.ModuleWhitelist"/>.
+    /// </summary>
+    /// <param name="ent">The borg to modify.</param>
+    /// <param name="whitelist">The new module whitelist.</param>
+    public void SetModuleWhitelist(Entity<BorgChassisComponent> ent, EntityWhitelist? whitelist)
+    {
+        ent.Comp.ModuleWhitelist = whitelist;
+    }
 }
index 781f847be35ea2c232cbaf4e6b66421d0ec4d8f1..b4ba140044153f903aa9e05df710345b93f22b3a 100644 (file)
@@ -8,6 +8,7 @@ using Content.Server.DeviceNetwork;
 using Content.Server.DeviceNetwork.Components;
 using Content.Server.DeviceNetwork.Systems;
 using Content.Server.Explosion.Components;
+using Robust.Shared.Utility;
 
 namespace Content.Server.Silicons.Borgs;
 
@@ -134,4 +135,20 @@ public sealed partial class BorgSystem
 
         return false;
     }
+
+    /// <summary>
+    /// Sets <see cref="BorgTransponderComponent.Sprite"/>.
+    /// </summary>
+    public void SetTransponderSprite(Entity<BorgTransponderComponent> ent, SpriteSpecifier sprite)
+    {
+        ent.Comp.Sprite = sprite;
+    }
+
+    /// <summary>
+    /// Sets <see cref="BorgTransponderComponent.Name"/>.
+    /// </summary>
+    public void SetTransponderName(Entity<BorgTransponderComponent> ent, string name)
+    {
+        ent.Comp.Name = name;
+    }
 }
index d0e9f80e36453149941f75d7965791d970f8077e..40c2c3bf3324dc12983dfb4fa2e0be1ea1cf0aa3 100644 (file)
@@ -82,6 +82,9 @@ public sealed partial class BorgSystem
         if (!component.ModuleContainer.Contains(module))
             return;
 
+        if (!CanRemoveModule((uid, component), (module, Comp<BorgModuleComponent>(module)), args.Actor))
+            return;
+
         _adminLog.Add(LogType.Action, LogImpact.Medium,
             $"{ToPrettyString(args.Actor):player} removed module {ToPrettyString(module)} from borg {ToPrettyString(uid)}");
         _container.Remove(module, component.ModuleContainer);
index bd85282a0f5e0a934b9d6172be380e551474d98d..ff204bfa8ceb0c351b5a1f72362ca479bd6f3cd6 100644 (file)
@@ -129,7 +129,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
 
         if (module != null && CanInsertModule(uid, used, component, module, args.User))
         {
-            _container.Insert(used, component.ModuleContainer);
+            InsertModule((uid, component), used);
             _adminLog.Add(LogType.Action, LogImpact.Low,
                 $"{ToPrettyString(args.User):player} installed module {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
             args.Handled = true;
@@ -137,6 +137,19 @@ public sealed partial class BorgSystem : SharedBorgSystem
         }
     }
 
+    /// <summary>
+    /// Inserts a new module into a borg, the same as if a player inserted it manually.
+    /// </summary>
+    /// <para>
+    /// This does not run checks to see if the borg is actually allowed to be inserted, such as whitelists.
+    /// </para>
+    /// <param name="ent">The borg to insert into.</param>
+    /// <param name="module">The module to insert.</param>
+    public void InsertModule(Entity<BorgChassisComponent> ent, EntityUid module)
+    {
+        _container.Insert(module, ent.Comp.ModuleContainer);
+    }
+
     // todo: consider transferring over the ghost role? managing that might suck.
     protected override void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
     {
index 20c079dfd8c8d07c35f27ed0b8972710e6d3be78..8df0035fc982dd6e0f205c5ffa47c96e4368927f 100644 (file)
@@ -159,4 +159,26 @@ public sealed class InteractionPopupSystem : EntitySystem
             _audio.PlayEntity(sfx, Filter.Empty().FromEntities(target), target, false);
         }
     }
+
+    /// <summary>
+    /// Sets <see cref="InteractionPopupComponent.InteractSuccessString"/>.
+    /// </summary>
+    /// <para>
+    /// This field is not networked automatically, so this method must be called on both sides of the network.
+    /// </para>
+    public void SetInteractSuccessString(Entity<InteractionPopupComponent> ent, string str)
+    {
+        ent.Comp.InteractSuccessString = str;
+    }
+
+    /// <summary>
+    /// Sets <see cref="InteractionPopupComponent.InteractFailureString"/>.
+    /// </summary>
+    /// <para>
+    /// This field is not networked automatically, so this method must be called on both sides of the network.
+    /// </para>
+    public void SetInteractFailureString(Entity<InteractionPopupComponent> ent, string str)
+    {
+        ent.Comp.InteractFailureString = str;
+    }
 }
index 629cf1169c4584c95debdcb954839665312f6ee3..61e0114ff24b5b9de46075bc2b937b0ec764a3f0 100644 (file)
@@ -7,10 +7,12 @@ namespace Content.Shared.Inventory;
 
 [RegisterComponent, NetworkedComponent]
 [Access(typeof(InventorySystem))]
+[AutoGenerateComponentState(true)]
 public sealed partial class InventoryComponent : Component
 {
     [DataField("templateId", customTypeSerializer: typeof(PrototypeIdSerializer<InventoryTemplatePrototype>))]
-    public string TemplateId { get; private set; } = "human";
+    [AutoNetworkedField]
+    public string TemplateId { get; set; } = "human";
 
     [DataField("speciesId")] public string? SpeciesId { get; set; }
 
@@ -32,3 +34,9 @@ public sealed partial class InventoryComponent : Component
     [DataField]
     public Dictionary<string, DisplacementData> MaleDisplacements = new();
 }
+
+/// <summary>
+/// Raised if the <see cref="InventoryComponent.TemplateId"/> of an inventory changed.
+/// </summary>
+[ByRefEvent]
+public struct InventoryTemplateUpdated;
index 2522dd5d0a3388c3b4c3fe38518097d5afa1c6f9..04d58c1cd52d63ff9f8905bee82901ec9f6e23d8 100644 (file)
@@ -1,4 +1,5 @@
 using System.Diagnostics.CodeAnalysis;
+using System.Linq;
 using Content.Shared.Inventory.Events;
 using Content.Shared.Storage;
 using Robust.Shared.Containers;
@@ -19,6 +20,8 @@ public partial class InventorySystem : EntitySystem
 
         _vvm.GetTypeHandler<InventoryComponent>()
             .AddHandler(HandleViewVariablesSlots, ListViewVariablesSlots);
+
+        SubscribeLocalEvent<InventoryComponent, AfterAutoHandleStateEvent>(AfterAutoState);
     }
 
     private void ShutdownSlots()
@@ -68,6 +71,27 @@ public partial class InventorySystem : EntitySystem
         }
     }
 
+    private void AfterAutoState(Entity<InventoryComponent> ent, ref AfterAutoHandleStateEvent args)
+    {
+        UpdateInventoryTemplate(ent);
+    }
+
+    protected virtual void UpdateInventoryTemplate(Entity<InventoryComponent> ent)
+    {
+        if (ent.Comp.LifeStage < ComponentLifeStage.Initialized)
+            return;
+
+        if (!_prototypeManager.TryIndex(ent.Comp.TemplateId, out InventoryTemplatePrototype? invTemplate))
+            return;
+
+        DebugTools.Assert(ent.Comp.Slots.Length == invTemplate.Slots.Length);
+
+        ent.Comp.Slots = invTemplate.Slots;
+
+        var ev = new InventoryTemplateUpdated();
+        RaiseLocalEvent(ent, ref ev);
+    }
+
     private void OnOpenSlotStorage(OpenSlotStorageNetworkMessage ev, EntitySessionEventArgs args)
     {
         if (args.SenderSession.AttachedEntity is not { Valid: true } uid)
@@ -170,6 +194,31 @@ public partial class InventorySystem : EntitySystem
         }
     }
 
+    /// <summary>
+    /// Change the inventory template ID an entity is using. The new template must be compatible.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// For an inventory template to be compatible with another, it must have exactly the same slot names.
+    /// All other changes are rejected.
+    /// </para>
+    /// </remarks>
+    /// <param name="ent">The entity to update.</param>
+    /// <param name="newTemplate">The ID of the new inventory template prototype.</param>
+    /// <exception cref="ArgumentException">
+    /// Thrown if the new template is not compatible with the existing one.
+    /// </exception>
+    public void SetTemplateId(Entity<InventoryComponent> ent, ProtoId<InventoryTemplatePrototype> newTemplate)
+    {
+        var newPrototype = _prototypeManager.Index(newTemplate);
+
+        if (!newPrototype.Slots.Select(x => x.Name).SequenceEqual(ent.Comp.Slots.Select(x => x.Name)))
+            throw new ArgumentException("Incompatible inventory template!");
+
+        ent.Comp.TemplateId = newTemplate;
+        Dirty(ent);
+    }
+
     /// <summary>
     /// Enumerator for iterating over an inventory's slot containers. Also has methods that skip empty containers.
     /// It should be safe to add or remove items while enumerating.
index cb4f0fe7dd49170cc90a251070060b9dca7d5003..3f27885db18c1f6179b4d2684ba2dcc165d79755 100644 (file)
@@ -9,12 +9,14 @@ namespace Content.Shared.Overlays;
 /// This component allows you to see health bars above damageable mobs.
 /// </summary>
 [RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState(true)]
 public sealed partial class ShowHealthBarsComponent : Component
 {
     /// <summary>
     /// Displays health bars of the damage containers.
     /// </summary>
     [DataField]
+    [AutoNetworkedField]
     public List<ProtoId<DamageContainerPrototype>> DamageContainers = new()
     {
         "Biological"
index aa12c9887a8e18a83467ecb38ac7771c1105fd5f..bc8b5419d2a1094f62b785feb059b99ea1edf024 100644 (file)
@@ -8,12 +8,14 @@ namespace Content.Shared.Overlays;
 /// This component allows you to see health status icons above damageable mobs.
 /// </summary>
 [RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState(true)]
 public sealed partial class ShowHealthIconsComponent : Component
 {
     /// <summary>
     /// Displays health status icons of the damage containers.
     /// </summary>
     [DataField]
+    [AutoNetworkedField]
     public List<ProtoId<DamageContainerPrototype>> DamageContainers = new()
     {
         "Biological"
diff --git a/Content.Shared/Silicons/Borgs/BorgTypePrototype.cs b/Content.Shared/Silicons/Borgs/BorgTypePrototype.cs
new file mode 100644 (file)
index 0000000..6154c12
--- /dev/null
@@ -0,0 +1,155 @@
+using Content.Shared.Interaction.Components;
+using Content.Shared.Inventory;
+using Content.Shared.Radio;
+using Content.Shared.Silicons.Borgs.Components;
+using Content.Shared.Whitelist;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Silicons.Borgs;
+
+/// <summary>
+/// Information for a borg type that can be selected by <see cref="BorgSwitchableTypeComponent"/>.
+/// </summary>
+/// <seealso cref="SharedBorgSwitchableTypeSystem"/>
+[Prototype]
+public sealed partial class BorgTypePrototype : IPrototype
+{
+    [ValidatePrototypeId<SoundCollectionPrototype>]
+    private static readonly ProtoId<SoundCollectionPrototype> DefaultFootsteps = new("FootstepBorg");
+
+    [IdDataField]
+    public required string ID { get; init; }
+
+    //
+    // Description info (name/desc) is configured via localization strings directly.
+    //
+
+    /// <summary>
+    /// The prototype displayed in the selection menu for this type.
+    /// </summary>
+    [DataField]
+    public required EntProtoId DummyPrototype { get; init; }
+
+    //
+    // Functional information
+    //
+
+    /// <summary>
+    /// The amount of free module slots this borg type has.
+    /// </summary>
+    /// <remarks>
+    /// This count is on top of the modules specified in <see cref="DefaultModules"/>.
+    /// </remarks>
+    /// <seealso cref="BorgChassisComponent.ModuleCount"/>
+    [DataField]
+    public int ExtraModuleCount { get; set; } = 0;
+
+    /// <summary>
+    /// The whitelist for borg modules that can be inserted into this borg type.
+    /// </summary>
+    /// <seealso cref="BorgChassisComponent.ModuleWhitelist"/>
+    [DataField]
+    public EntityWhitelist? ModuleWhitelist { get; set; }
+
+    /// <summary>
+    /// Inventory template used by this borg.
+    /// </summary>
+    /// <remarks>
+    /// This template must be compatible with the normal borg templates,
+    /// so in practice it can only be used to differentiate the visual position of the slots on the character sprites.
+    /// </remarks>
+    /// <seealso cref="InventorySystem.SetTemplateId"/>
+    [DataField]
+    public ProtoId<InventoryTemplatePrototype> InventoryTemplateId { get; set; } = "borgShort";
+
+    /// <summary>
+    /// Radio channels that this borg will gain access to from this module.
+    /// </summary>
+    /// <remarks>
+    /// These channels are provided on top of the ones specified in
+    /// <see cref="BorgSwitchableTypeComponent.InherentRadioChannels"/>.
+    /// </remarks>
+    [DataField]
+    public ProtoId<RadioChannelPrototype>[] RadioChannels = [];
+
+    /// <summary>
+    /// Borg module types that are always available to borgs of this type.
+    /// </summary>
+    /// <remarks>
+    /// These modules still work like modules, although they cannot be removed from the borg.
+    /// </remarks>
+    /// <seealso cref="BorgModuleComponent.DefaultModule"/>
+    [DataField]
+    public EntProtoId[] DefaultModules = [];
+
+    /// <summary>
+    /// Additional components to add to the borg entity when this type is selected.
+    /// </summary>
+    [DataField]
+    public ComponentRegistry? AddComponents { get; set; }
+
+    //
+    // Visual information
+    //
+
+    /// <summary>
+    /// The sprite state for the main borg body.
+    /// </summary>
+    [DataField]
+    public string SpriteBodyState { get; set; } = "robot";
+
+    /// <summary>
+    /// An optional movement sprite state for the main borg body.
+    /// </summary>
+    [DataField]
+    public string? SpriteBodyMovementState { get; set; }
+
+    /// <summary>
+    /// Sprite state used to indicate that the borg has a mind in it.
+    /// </summary>
+    /// <seealso cref="BorgChassisComponent.HasMindState"/>
+    [DataField]
+    public string SpriteHasMindState { get; set; } = "robot_e";
+
+    /// <summary>
+    /// Sprite state used to indicate that the borg has no mind in it.
+    /// </summary>
+    /// <seealso cref="BorgChassisComponent.NoMindState"/>
+    [DataField]
+    public string SpriteNoMindState { get; set; } = "robot_e_r";
+
+    /// <summary>
+    /// Sprite state used when the borg's flashlight is on.
+    /// </summary>
+    [DataField]
+    public string SpriteToggleLightState { get; set; } = "robot_l";
+
+    //
+    // Minor information
+    //
+
+    /// <summary>
+    /// String to use on petting success.
+    /// </summary>
+    /// <seealso cref="InteractionPopupComponent"/>
+    [DataField]
+    public string PetSuccessString { get; set; } = "petting-success-generic-cyborg";
+
+    /// <summary>
+    /// String to use on petting failure.
+    /// </summary>
+    /// <seealso cref="InteractionPopupComponent"/>
+    [DataField]
+    public string PetFailureString { get; set; } = "petting-failure-generic-cyborg";
+
+    //
+    // Sounds
+    //
+
+    /// <summary>
+    /// Sound specifier for footstep sounds created by this borg.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier FootstepCollection { get; set; } = new SoundCollectionSpecifier(DefaultFootsteps);
+}
index de0fe0bce381812d2a951627dcef465f20c78891..c2bf2b2801b1567ed51c88e42b1415091c5c5b3c 100644 (file)
@@ -89,5 +89,18 @@ public enum BorgVisuals : byte
 [Serializable, NetSerializable]
 public enum BorgVisualLayers : byte
 {
-    Light
+    /// <summary>
+    /// Main borg body layer.
+    /// </summary>
+    Body,
+
+    /// <summary>
+    /// Layer for the borg's mind state.
+    /// </summary>
+    Light,
+
+    /// <summary>
+    /// Layer for the borg flashlight status.
+    /// </summary>
+    LightStatus,
 }
index a7523c1ce7021d10c3dcbd896fac629db5cb12da..e542a1e3e3e8e41edf2995dc610f365cecf6f0cc 100644 (file)
@@ -7,6 +7,7 @@ namespace Content.Shared.Silicons.Borgs.Components;
 /// to give them unique abilities and attributes.
 /// </summary>
 [RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
+[AutoGenerateComponentState]
 public sealed partial class BorgModuleComponent : Component
 {
     /// <summary>
@@ -16,6 +17,13 @@ public sealed partial class BorgModuleComponent : Component
     public EntityUid? InstalledEntity;
 
     public bool Installed => InstalledEntity != null;
+
+    /// <summary>
+    /// If true, this is a "default" module that cannot be removed from a borg.
+    /// </summary>
+    [DataField]
+    [AutoNetworkedField]
+    public bool DefaultModule;
 }
 
 /// <summary>
diff --git a/Content.Shared/Silicons/Borgs/Components/BorgSwitchableTypeComponent.cs b/Content.Shared/Silicons/Borgs/Components/BorgSwitchableTypeComponent.cs
new file mode 100644 (file)
index 0000000..9a783d1
--- /dev/null
@@ -0,0 +1,72 @@
+using Content.Shared.Actions;
+using Content.Shared.Radio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Silicons.Borgs.Components;
+
+/// <summary>
+/// Component for borgs that can switch their "type" after being created.
+/// </summary>
+/// <remarks>
+/// <para>
+/// This is used by all NT borgs, on construction and round-start spawn.
+/// Borgs are effectively useless until they have made their choice of type.
+/// Borg type selections are currently irreversible.
+/// </para>
+/// <para>
+/// Available types are specified in <see cref="BorgTypePrototype"/>s.
+/// </para>
+/// </remarks>
+/// <seealso cref="SharedBorgSwitchableTypeSystem"/>
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState(raiseAfterAutoHandleState: true)]
+[Access(typeof(SharedBorgSwitchableTypeSystem))]
+public sealed partial class BorgSwitchableTypeComponent : Component
+{
+    /// <summary>
+    /// Action entity used by players to select their type.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntityUid? SelectTypeAction;
+
+    /// <summary>
+    /// The currently selected borg type, if any.
+    /// </summary>
+    /// <remarks>
+    /// This can be set in a prototype to immediately apply a borg type, and not have switching support.
+    /// </remarks>
+    [DataField, AutoNetworkedField]
+    public ProtoId<BorgTypePrototype>? SelectedBorgType;
+
+    /// <summary>
+    /// Radio channels that the borg will always have. These are added on top of the selected type's radio channels.
+    /// </summary>
+    [DataField]
+    public ProtoId<RadioChannelPrototype>[] InherentRadioChannels = [];
+}
+
+/// <summary>
+/// Action event used to open the selection menu of a <see cref="BorgSwitchableTypeComponent"/>.
+/// </summary>
+public sealed partial class BorgToggleSelectTypeEvent : InstantActionEvent;
+
+/// <summary>
+/// UI message used by a borg to select their type with <see cref="BorgSwitchableTypeComponent"/>.
+/// </summary>
+/// <param name="prototype">The borg type prototype that the user selected.</param>
+[Serializable, NetSerializable]
+public sealed class BorgSelectTypeMessage(ProtoId<BorgTypePrototype> prototype) : BoundUserInterfaceMessage
+{
+    public ProtoId<BorgTypePrototype> Prototype = prototype;
+}
+
+/// <summary>
+/// UI key used by the selection menu for <see cref="BorgSwitchableTypeComponent"/>.
+/// </summary>
+[NetSerializable, Serializable]
+public enum BorgSwitchableTypeUiKey : byte
+{
+    SelectBorgType,
+}
diff --git a/Content.Shared/Silicons/Borgs/SharedBorgSwitchableTypeSystem.cs b/Content.Shared/Silicons/Borgs/SharedBorgSwitchableTypeSystem.cs
new file mode 100644 (file)
index 0000000..d9abeb2
--- /dev/null
@@ -0,0 +1,125 @@
+using Content.Shared.Actions;
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Components;
+using Content.Shared.Movement.Components;
+using Content.Shared.Silicons.Borgs.Components;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Silicons.Borgs;
+
+/// <summary>
+/// Implements borg type switching.
+/// </summary>
+/// <seealso cref="BorgSwitchableTypeComponent"/>
+public abstract class SharedBorgSwitchableTypeSystem : EntitySystem
+{
+    // TODO: Allow borgs to be reset to default configuration.
+
+    [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
+    [Dependency] private readonly SharedUserInterfaceSystem _userInterface = default!;
+    [Dependency] protected readonly IPrototypeManager Prototypes = default!;
+    [Dependency] private readonly InteractionPopupSystem _interactionPopup = default!;
+
+    [ValidatePrototypeId<EntityPrototype>]
+    public const string ActionId = "ActionSelectBorgType";
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<BorgSwitchableTypeComponent, MapInitEvent>(OnMapInit);
+        SubscribeLocalEvent<BorgSwitchableTypeComponent, ComponentShutdown>(OnShutdown);
+        SubscribeLocalEvent<BorgSwitchableTypeComponent, BorgToggleSelectTypeEvent>(OnSelectBorgTypeAction);
+
+        Subs.BuiEvents<BorgSwitchableTypeComponent>(BorgSwitchableTypeUiKey.SelectBorgType,
+            sub =>
+            {
+                sub.Event<BorgSelectTypeMessage>(SelectTypeMessageHandler);
+            });
+    }
+
+    //
+    // UI-adjacent code
+    //
+
+    private void OnMapInit(Entity<BorgSwitchableTypeComponent> ent, ref MapInitEvent args)
+    {
+        _actionsSystem.AddAction(ent, ref ent.Comp.SelectTypeAction, ActionId);
+        Dirty(ent);
+
+        if (ent.Comp.SelectedBorgType != null)
+        {
+            SelectBorgModule(ent, ent.Comp.SelectedBorgType.Value);
+        }
+    }
+
+    private void OnShutdown(Entity<BorgSwitchableTypeComponent> ent, ref ComponentShutdown args)
+    {
+        _actionsSystem.RemoveAction(ent, ent.Comp.SelectTypeAction);
+    }
+
+    private void OnSelectBorgTypeAction(Entity<BorgSwitchableTypeComponent> ent, ref BorgToggleSelectTypeEvent args)
+    {
+        if (args.Handled || !TryComp<ActorComponent>(ent, out var actor))
+            return;
+
+        args.Handled = true;
+
+        _userInterface.TryToggleUi((ent.Owner, null), BorgSwitchableTypeUiKey.SelectBorgType, actor.PlayerSession);
+    }
+
+    private void SelectTypeMessageHandler(Entity<BorgSwitchableTypeComponent> ent, ref BorgSelectTypeMessage args)
+    {
+        if (ent.Comp.SelectedBorgType != null)
+            return;
+
+        if (!Prototypes.HasIndex(args.Prototype))
+            return;
+
+        SelectBorgModule(ent, args.Prototype);
+    }
+
+    //
+    // Implementation
+    //
+
+    protected virtual void SelectBorgModule(
+        Entity<BorgSwitchableTypeComponent> ent,
+        ProtoId<BorgTypePrototype> borgType)
+    {
+        ent.Comp.SelectedBorgType = borgType;
+
+        _actionsSystem.RemoveAction(ent, ent.Comp.SelectTypeAction);
+        ent.Comp.SelectTypeAction = null;
+        Dirty(ent);
+
+        _userInterface.CloseUi((ent.Owner, null), BorgSwitchableTypeUiKey.SelectBorgType);
+
+        UpdateEntityAppearance(ent);
+    }
+
+    protected void UpdateEntityAppearance(Entity<BorgSwitchableTypeComponent> entity)
+    {
+        if (!Prototypes.TryIndex(entity.Comp.SelectedBorgType, out var proto))
+            return;
+
+        UpdateEntityAppearance(entity, proto);
+    }
+
+    protected virtual void UpdateEntityAppearance(
+        Entity<BorgSwitchableTypeComponent> entity,
+        BorgTypePrototype prototype)
+    {
+        if (TryComp(entity, out InteractionPopupComponent? popup))
+        {
+            _interactionPopup.SetInteractSuccessString((entity.Owner, popup), prototype.PetSuccessString);
+            _interactionPopup.SetInteractFailureString((entity.Owner, popup), prototype.PetFailureString);
+        }
+
+        if (TryComp(entity, out FootstepModifierComponent? footstepModifier))
+        {
+            footstepModifier.FootstepSoundCollection = prototype.FootstepCollection;
+        }
+    }
+}
index c62e63481d62ad2a7be2ab261a547d44a273d6cc..827bb351b0784c9b529fe0d9abef6a5faee730e9 100644 (file)
@@ -124,4 +124,13 @@ public abstract partial class SharedBorgSystem : EntitySystem
         var sprintDif = movement.BaseWalkSpeed / movement.BaseSprintSpeed;
         args.ModifySpeed(1f, sprintDif);
     }
+
+    /// <summary>
+    /// Sets <see cref="BorgModuleComponent.DefaultModule"/>.
+    /// </summary>
+    public void SetBorgModuleDefault(Entity<BorgModuleComponent> ent, bool newDefault)
+    {
+        ent.Comp.DefaultModule = newDefault;
+        Dirty(ent);
+    }
 }
index 6c495510b05aeec48645ea85efe792581a3b2bb1..9c9dc71069a616e6943e94fd82a68cd96bd93378 100644 (file)
@@ -25,3 +25,40 @@ borg-transponder-disabling-popup = Your transponder begins to lock you out of th
 borg-transponder-destroying-popup = The self destruct of {$name} starts beeping!
 borg-transponder-emagged-disabled-popup = Your transponder's lights go out!
 borg-transponder-emagged-destroyed-popup = Your transponder's fuse blows!
+
+## Borg type selection UI.
+borg-select-type-menu-title = Select Chassis Type
+borg-select-type-menu-bottom-text = Chassis selection is irreversible
+borg-select-type-menu-available = Available types
+borg-select-type-menu-information = Information
+borg-select-type-menu-select-type = Select type to view information
+borg-select-type-menu-confirm = Confirm selection
+borg-select-type-menu-guidebook = Guidebook
+
+## Borg type information
+
+borg-type-generic-name = Generic
+borg-type-generic-desc = Jack of all trades, master of none. Do various random station tasks, or maybe help out the science department that built you.
+borg-type-generic-transponder = generic cyborg
+
+borg-type-engineering-name = Engineering
+borg-type-engineering-desc = Assist the engineering team in station construction, repairing damage, or fixing electrical and atmospheric issues.
+borg-type-engineering-transponder = engineering cyborg
+
+borg-type-mining-name = Salvage
+borg-type-mining-desc = Join salvage and help them mine for materials, scavenge wrecks, and fight off hostile wildlife.
+borg-type-mining-transponder = salvage cyborg
+
+borg-type-janitor-name = Janitor
+borg-type-janitor-desc = Keep the station nice and tidy, clean up spills, collect and properly dispose of trash left around by lazy crewmembers.
+borg-type-janitor-transponder = janitor cyborg
+
+borg-type-medical-name = Medical
+borg-type-medical-desc = Provide medical attention to crew who need it, either in medbay or in hazardous areas conventional paramedics cannot reach.
+borg-type-medical-transponder = medical cyborg
+
+borg-type-service-name = Service
+borg-type-service-desc = Help out with a wide range of crew services, ranging from serving snacks and drinks to botany to entertainment.
+borg-type-service-transponder = service cyborg
+
+
index a0168ef00fc4f2f81764c596adbd1daffb0da316..0f635ba3ec67171d4c2f0e81524d31e84d8b86cd 100644 (file)
       state: state-laws
     event: !type:ToggleLawsScreenEvent
     useDelay: 0.5
+
+- type: entity
+  id: ActionSelectBorgType
+  name: Select Cyborg Type
+  components:
+  - type: InstantAction
+    itemIconStyle: NoItem
+    icon:
+      sprite: Interface/Actions/actions_borg.rsi
+      state: select-type
+    event: !type:BorgToggleSelectTypeEvent
+    useDelay: 0.5
index 6b2b3f57d264acd40aec3bac82427635ec9fe135..3b0f2540963ded1e8227e2e077446f37830dc11b 100644 (file)
     - Robotics
 
 - type: entity
-  id: BaseBorgArmLeft
+  id: LeftArmBorg
   parent: PartSilicon
   name: cyborg left arm
-  abstract: true
   components:
   - type: BodyPart
     partType: Hand
     symmetry: Left
+  - type: Sprite
+    state: borg_l_arm
+  - type: Icon
+    state: borg_l_arm
   - type: Tag
     tags:
     - Trash
     - BorgArm
+    - BorgLArm
 
 - type: entity
-  id: BaseBorgArmRight
+  id: RightArmBorg
   parent: PartSilicon
   name: cyborg right arm
-  abstract: true
   components:
   - type: BodyPart
     partType: Hand
     symmetry: Right
+  - type: Sprite
+    state: borg_r_arm
+  - type: Icon
+    state: borg_r_arm
   - type: Tag
     tags:
     - Trash
     - BorgArm
+    - BorgRArm
 
 - type: entity
-  id: BaseBorgLegLeft
+  id: LeftLegBorg
   parent: PartSilicon
   name: cyborg left leg
-  abstract: true
   components:
   - type: BodyPart
     partType: Leg
     symmetry: Left
+  - type: Sprite
+    state: borg_l_leg
+  - type: Icon
+    state: borg_l_leg
   - type: Tag
     tags:
     - Trash
     - BorgLeg
+    - BorgLLeg
 
 - type: entity
-  id: BaseBorgLegRight
+  id: RightLegBorg
   parent: PartSilicon
   name: cyborg right leg
-  abstract: true
   components:
   - type: BodyPart
     partType: Leg
     symmetry: Right
+  - type: Sprite
+    state: borg_r_leg
+  - type: Icon
+    state: borg_r_leg
   - type: Tag
     tags:
     - Trash
     - BorgLeg
+    - BorgRLeg
 
 - type: entity
-  id: BaseBorgHead
+  id: LightHeadBorg
   parent: PartSilicon
   name: cyborg head
-  abstract: true
   components:
   - type: BodyPart
     partType: Head
+  - type: Sprite
+    state: borg_head
+  - type: Icon
+    state: borg_head
   - type: Tag
     tags:
     - Trash
     - BorgHead
 
 - type: entity
-  id: BaseBorgTorso
+  id: TorsoBorg
   parent: PartSilicon
   name: cyborg torso
-  abstract: true
   components:
   - type: BodyPart
     partType: Torso
+  - type: Sprite
+    state: borg_chest
+  - type: Icon
+    state: borg_chest
   - type: Tag
     tags:
     - Trash
+    - BorgTorso
index 0db92ac941d47fdaf123c40fb11739e5d1a67d30..9303bd42dd553d4efb6423e90d238a45b2ab06a8 100644 (file)
@@ -69,6 +69,9 @@
         type: BorgBoundUserInterface
       enum.StrippingUiKey.Key:
         type: StrippableBoundUserInterface
+      # Only used for NT borgs that can switch type, defined here to avoid copy-pasting the rest of this component.
+      enum.BorgSwitchableTypeUiKey.SelectBorgType:
+        type: BorgSelectTypeUserInterface
   - type: ActivatableUI
     key: enum.BorgUiKey.Key
   - type: SiliconLawBound
       collection: FootstepBorg
   - type: Construction
     graph: Cyborg
+    node: cyborg
     containers:
     - part-container
     - cell_slot
   - type: AccessReader
     access: [["Command"], ["Research"]]
   - type: ShowJobIcons
+  - type: InteractionPopup
+    interactSuccessSound:
+      path: /Audio/Ambience/Objects/periodic_beep.ogg
 
 - type: entity
   id: BaseBorgChassisSyndicate
index 6a8f1e5abb08303be361dd4b9981597f2156e153..fa324c0124fde3ff92df9626103ad9d7d3d7fa9b 100644 (file)
@@ -1,23 +1,22 @@
 - type: entity
-  id: BorgChassisGeneric
+  id: BorgChassisSelectable
   parent: BaseBorgChassisNT
   components:
   - type: Sprite
     layers:
     - state: robot
+      map: ["enum.BorgVisualLayers.Body", "movement"]
     - state: robot_e_r
       map: ["enum.BorgVisualLayers.Light"]
       shader: unshaded
       visible: false
     - state: robot_l
       shader: unshaded
-      map: ["light"]
+      map: ["light","enum.BorgVisualLayers.LightStatus"]
       visible: false
   - type: BorgChassis
-    maxModules: 6
-    moduleWhitelist:
-      tags:
-      - BorgModuleGeneric
+    # Default borg can take no modules until selected type.
+    maxModules: 0
     hasMindState: robot_e
     noMindState: robot_e_r
   - type: BorgTransponder
       sprite: Mobs/Silicon/chassis.rsi
       state: robot
     name: cyborg
-  - type: Construction
-    node: cyborg
-  - type: Speech
-    speechVerb: Robotic
   - type: InteractionPopup
     interactSuccessString: petting-success-generic-cyborg
     interactFailureString: petting-failure-generic-cyborg
-    interactSuccessSound:
-      path: /Audio/Ambience/Objects/periodic_beep.ogg
+  - type: BorgSwitchableType
+    inherentRadioChannels:
+    - Common
+    - Binary
+
+- type: entity
+  id: BorgChassisGeneric
+  parent: BorgChassisSelectable
+  name: generic cyborg
+  suffix: type picked
+  components:
+  - type: BorgSwitchableType
+    selectedBorgType: generic
 
 - type: entity
   id: BorgChassisMining
-  parent: BaseBorgChassisNT
+  parent: BorgChassisSelectable
   name: salvage cyborg
   components:
-  - type: Sprite
-    layers:
-    - state: miner
-      map: ["movement"]
-    - state: miner_e_r
-      map: ["enum.BorgVisualLayers.Light"]
-      shader: unshaded
-      visible: false
-    - state: miner_l
-      shader: unshaded
-      map: ["light"]
-      visible: false
-  - type: SpriteMovement
-    movementLayers:
-      movement:
-        state: miner_moving
-    noMovementLayers:
-      movement:
-        state: miner
-  - type: BorgChassis
-    maxModules: 4
-    moduleWhitelist:
-      tags:
-      - BorgModuleGeneric
-      - BorgModuleCargo
-    hasMindState: miner_e
-    noMindState: miner_e_r
-  - type: BorgTransponder
-    sprite:
-      sprite: Mobs/Silicon/chassis.rsi
-      state: miner
-    name: salvage cyborg
-  - type: Construction
-    node: mining
-  - type: IntrinsicRadioTransmitter
-    channels:
-    - Supply
-    - Binary
-    - Common
-    - Science
-  - type: ActiveRadio
-    channels:
-    - Supply
-    - Binary
-    - Common
-    - Science
-  - type: AccessReader
-    access: [["Cargo"], ["Salvage"], ["Command"], ["Research"]]
-  - type: Inventory
-    templateId: borgTall
-  - type: InteractionPopup
-    interactSuccessString: petting-success-salvage-cyborg
-    interactFailureString: petting-failure-salvage-cyborg
-    interactSuccessSound:
-      path: /Audio/Ambience/Objects/periodic_beep.ogg
+  - type: BorgSwitchableType
+    selectedBorgType: mining
 
 - type: entity
   id: BorgChassisEngineer
-  parent: BaseBorgChassisNT
+  parent: BorgChassisSelectable
   name: engineer cyborg
   components:
-  - type: Sprite
-    layers:
-    - state: engineer
-    - state: engineer_e_r
-      map: ["enum.BorgVisualLayers.Light"]
-      shader: unshaded
-      visible: false
-    - state: engineer_l
-      shader: unshaded
-      map: ["light"]
-      visible: false
-  - type: BorgChassis
-    maxModules: 4
-    moduleWhitelist:
-      tags:
-      - BorgModuleGeneric
-      - BorgModuleEngineering
-    hasMindState: engineer_e
-    noMindState: engineer_e_r
-  - type: BorgTransponder
-    sprite:
-      sprite: Mobs/Silicon/chassis.rsi
-      state: engineer
-    name: engineer cyborg
-  - type: Construction
-    node: engineer
-  - type: IntrinsicRadioTransmitter
-    channels:
-    - Engineering
-    - Binary
-    - Common
-    - Science
-  - type: ActiveRadio
-    channels:
-    - Engineering
-    - Binary
-    - Common
-    - Science
-  - type: AccessReader
-    access: [["Engineering"], ["Command"], ["Research"]]
-  - type: Inventory
-    templateId: borgShort
-  - type: InteractionPopup
-    interactSuccessString: petting-success-engineer-cyborg
-    interactFailureString: petting-failure-engineer-cyborg
-    interactSuccessSound:
-      path: /Audio/Ambience/Objects/periodic_beep.ogg
+  - type: BorgSwitchableType
+    selectedBorgType: engineering
 
 - type: entity
   id: BorgChassisJanitor
-  parent: BaseBorgChassisNT
+  parent: BorgChassisSelectable
   name: janitor cyborg
   components:
-  - type: Sprite
-    layers:
-    - state: janitor
-      map: ["movement"]
-    - state: janitor_e_r
-      map: ["enum.BorgVisualLayers.Light"]
-      shader: unshaded
-      visible: false
-    - state: janitor_l
-      shader: unshaded
-      map: ["light"]
-      visible: false
-  - type: SpriteMovement
-    movementLayers:
-      movement:
-        state: janitor_moving
-    noMovementLayers:
-      movement:
-        state: janitor
-  - type: BorgChassis
-    maxModules: 4
-    moduleWhitelist:
-      tags:
-      - BorgModuleGeneric
-      - BorgModuleJanitor
-    hasMindState: janitor_e
-    noMindState: janitor_e_r
-  - type: BorgTransponder
-    sprite:
-      sprite: Mobs/Silicon/chassis.rsi
-      state: janitor
-    name: janitor cyborg
-  - type: Construction
-    node: janitor
-  - type: IntrinsicRadioTransmitter
-    channels:
-    - Service
-    - Binary
-    - Common
-    - Science
-  - type: ActiveRadio
-    channels:
-    - Service
-    - Binary
-    - Common
-    - Science
-  - type: AccessReader
-    access: [["Service"], ["Command"], ["Research"]]
-  - type: Inventory
-    templateId: borgShort
-  - type: InteractionPopup
-    interactSuccessString: petting-success-janitor-cyborg
-    interactFailureString: petting-failure-janitor-cyborg
-    interactSuccessSound:
-      path: /Audio/Ambience/Objects/periodic_beep.ogg
+  - type: BorgSwitchableType
+    selectedBorgType: janitor
 
 - type: entity
   id: BorgChassisMedical
-  parent: [BaseBorgChassisNT, ShowMedicalIcons]
+  parent: BorgChassisSelectable
   name: medical cyborg
   components:
-  - type: Sprite
-    layers:
-    - state: medical
-      map: ["movement"]
-    - state: medical_e_r
-      map: ["enum.BorgVisualLayers.Light"]
-      shader: unshaded
-      visible: false
-    - state: medical_l
-      shader: unshaded
-      map: ["light"]
-      visible: false
-  - type: SpriteMovement
-    movementLayers:
-      movement:
-        state: medical_moving
-    noMovementLayers:
-      movement:
-        state: medical
-  - type: BorgChassis
-    maxModules: 4
-    moduleWhitelist:
-      tags:
-      - BorgModuleGeneric
-      - BorgModuleMedical
-    hasMindState: medical_e
-    noMindState: medical_e_r
-  - type: BorgTransponder
-    sprite:
-      sprite: Mobs/Silicon/chassis.rsi
-      state: medical
-    name: medical cyborg
-  - type: Construction
-    node: medical
-  - type: IntrinsicRadioTransmitter
-    channels:
-    - Medical
-    - Binary
-    - Common
-    - Science
-  - type: ActiveRadio
-    channels:
-    - Medical
-    - Binary
-    - Common
-    - Science
-  - type: AccessReader
-    access: [["Medical"], ["Command"], ["Research"]]
-  - type: Inventory
-    templateId: borgDutch
-  - type: FootstepModifier
-    footstepSoundCollection:
-      collection: FootstepHoverBorg
-  - type: SolutionScanner
-  - type: InteractionPopup
-    interactSuccessString: petting-success-medical-cyborg
-    interactFailureString: petting-failure-medical-cyborg
-    interactSuccessSound:
-      path: /Audio/Ambience/Objects/periodic_beep.ogg
+  - type: BorgSwitchableType
+    selectedBorgType: medical
 
 - type: entity
   id: BorgChassisService
-  parent: BaseBorgChassisNT
+  parent: BorgChassisSelectable
   name: service cyborg
   components:
-  - type: Sprite
-    layers:
-    - state: service
-    - state: service_e_r
-      map: ["enum.BorgVisualLayers.Light"]
-      shader: unshaded
-      visible: false
-    - state: service_l
-      shader: unshaded
-      map: ["light"]
-      visible: false
-  - type: BorgChassis
-    maxModules: 4
-    moduleWhitelist:
-      tags:
-      - BorgModuleGeneric
-      - BorgModuleService
-    hasMindState: service_e
-    noMindState: service_e_r
-  - type: BorgTransponder
-    sprite:
-      sprite: Mobs/Silicon/chassis.rsi
-      state: service
-    name: service cyborg
-  - type: Construction
-    node: service
-  - type: IntrinsicRadioTransmitter
-    channels:
-    - Service
-    - Binary
-    - Common
-    - Science
-  - type: ActiveRadio
-    channels:
-    - Service
-    - Binary
-    - Common
-    - Science
-  - type: AccessReader
-    access: [["Service"], ["Command"], ["Research"]]
-  - type: Inventory
-    templateId: borgTall
-  - type: InteractionPopup
-    interactSuccessString: petting-success-service-cyborg
-    interactFailureString: petting-failure-service-cyborg
-    interactSuccessSound:
-      path: /Audio/Ambience/Objects/periodic_beep.ogg
+  - type: BorgSwitchableType
+    selectedBorgType: service
 
 - type: entity
   id: BorgChassisSyndicateAssault
           - BorgModuleSyndicateAssault
       hasMindState: synd_sec_e
       noMindState: synd_sec
-    - type: Construction
-      node: syndicateassault
     - type: InteractionPopup
       interactSuccessString: petting-success-syndicate-cyborg
       interactFailureString: petting-failure-syndicate-cyborg
           - BorgModuleSyndicate
       hasMindState: synd_medical_e
       noMindState: synd_medical
-    - type: Construction
-      node: syndicatemedical
     - type: ShowHealthBars
     - type: InteractionPopup
       interactSuccessString: petting-success-syndicate-cyborg
           - BorgModuleSyndicate
       hasMindState: synd_engi_e
       noMindState: synd_engi
-    - type: Construction
-      node: syndicatesaboteur
     - type: ShowHealthBars
       damageContainers:
       - Inorganic
index e787ef59f00a8f28f2897ac7d4e4ee4e52e5db4d..bcac46ed842ab8f240b6defb9402ce4f163ce8d6 100644 (file)
       map: ["base"]
 
 # Borgs
-- type: entity
-  id: PlayerBorgGeneric
-  parent: BorgChassisGeneric
-  suffix: Battery, Tools
-  components:
-  - type: ContainerFill
-    containers:
-      borg_brain:
-        - PositronicBrain
-      borg_module:
-        - BorgModuleTool
-  - type: ItemSlots
-    slots:
-      cell_slot:
-        name: power-cell-slot-component-slot-name-default
-        startingItem: PowerCellMedium
-  - type: RandomMetadata
-    nameSegments: [names_borg]
-
 - type: entity
   id: PlayerBorgBattery
-  parent: BorgChassisGeneric
+  parent: BorgChassisSelectable
   suffix: Battery
   components:
   - type: ContainerFill
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_parts.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_parts.yml
deleted file mode 100644 (file)
index 6df0488..0000000
+++ /dev/null
@@ -1,503 +0,0 @@
-# generic parts
-- type: entity
-  id: LeftArmBorg
-  parent: BaseBorgArmLeft
-  components:
-  - type: Sprite
-    state: borg_l_arm
-  - type: Icon
-    state: borg_l_arm
-  - type: Tag
-    tags:
-    - Trash
-    - BorgArm
-    - BorgGenericLArm
-
-- type: entity
-  id: RightArmBorg
-  parent: BaseBorgArmRight
-  components:
-  - type: Sprite
-    state: borg_r_arm
-  - type: Icon
-    state: borg_r_arm
-  - type: Tag
-    tags:
-    - Trash
-    - BorgArm
-    - BorgGenericRArm
-
-- type: entity
-  id: LeftLegBorg
-  parent: BaseBorgLegLeft
-  components:
-  - type: Sprite
-    state: borg_l_leg
-  - type: Icon
-    state: borg_l_leg
-  - type: Tag
-    tags:
-    - Trash
-    - BorgLeg
-    - BorgGenericLLeg
-
-- type: entity
-  id: RightLegBorg
-  parent: BaseBorgLegRight
-  components:
-  - type: Sprite
-    state: borg_r_leg
-  - type: Icon
-    state: borg_r_leg
-  - type: Tag
-    tags:
-    - Trash
-    - BorgLeg
-    - BorgGenericRLeg
-
-- type: entity
-  id: LightHeadBorg
-  parent: BaseBorgHead
-  components:
-  - type: Sprite
-    state: borg_head
-  - type: Icon
-    state: borg_head
-  - type: Tag
-    tags:
-    - Trash
-    - BorgHead
-    - BorgGenericHead
-
-- type: entity
-  id: TorsoBorg
-  parent: BaseBorgTorso
-  components:
-  - type: Sprite
-    state: borg_chest
-  - type: Icon
-    state: borg_chest
-  - type: Tag
-    tags:
-    - Trash
-    - BorgGenericTorso
-
-# engineer parts
-- type: entity
-  id: LeftArmBorgEngineer
-  parent: BaseBorgArmLeft
-  name: engineer cyborg left arm
-  components:
-  - type: Sprite
-    state: engineer_l_arm
-  - type: Icon
-    state: engineer_l_arm
-  - type: Tag
-    tags:
-    - Trash
-    - BorgArm
-    - BorgEngineerLArm
-
-- type: entity
-  id: RightArmBorgEngineer
-  parent: BaseBorgArmRight
-  name: engineer cyborg right arm
-  components:
-  - type: Sprite
-    state: engineer_r_arm
-  - type: Icon
-    state: engineer_r_arm
-  - type: Tag
-    tags:
-    - Trash
-    - BorgArm
-    - BorgEngineerRArm
-
-- type: entity
-  id: LeftLegBorgEngineer
-  parent: BaseBorgLegLeft
-  name: engineer cyborg left leg
-  components:
-  - type: Sprite
-    state: engineer_l_leg
-  - type: Icon
-    state: engineer_l_leg
-  - type: Tag
-    tags:
-    - Trash
-    - BorgLeg
-    - BorgEngineerLLeg
-
-- type: entity
-  id: RightLegBorgEngineer
-  parent: BaseBorgLegRight
-  name: engineer cyborg right leg
-  components:
-  - type: Sprite
-    state: engineer_r_leg
-  - type: Icon
-    state: engineer_r_leg
-  - type: Tag
-    tags:
-    - Trash
-    - BorgLeg
-    - BorgEngineerRLeg
-
-- type: entity
-  id: HeadBorgEngineer
-  parent: BaseBorgHead
-  name: engineer cyborg head
-  components:
-  - type: Sprite
-    state: engineer_head
-  - type: Icon
-    state: engineer_head
-  - type: Tag
-    tags:
-    - Trash
-    - BorgHead
-    - BorgEngineerHead
-
-- type: entity
-  id: TorsoBorgEngineer
-  parent: BaseBorgTorso
-  name: engineer cyborg torso
-  components:
-  - type: Sprite
-    state: engineer_chest
-  - type: Icon
-    state: engineer_chest
-  - type: Tag
-    tags:
-    - Trash
-    - BorgEngineerTorso
-
-# janitor parts
-- type: entity
-  id: LeftLegBorgJanitor
-  parent: BaseBorgLegLeft
-  name: janitor cyborg left leg
-  components:
-    - type: Sprite
-      state: janitor_l_leg
-    - type: Icon
-      state: janitor_l_leg
-    - type: Tag
-      tags:
-        - Trash
-        - BorgLeg
-        - BorgJanitorLLeg
-
-- type: entity
-  id: RightLegBorgJanitor
-  parent: BaseBorgLegRight
-  name: janitor cyborg right leg
-  components:
-    - type: Sprite
-      state: janitor_r_leg
-    - type: Icon
-      state: janitor_r_leg
-    - type: Tag
-      tags:
-        - Trash
-        - BorgLeg
-        - BorgJanitorRLeg
-
-- type: entity
-  id: HeadBorgJanitor
-  parent: BaseBorgHead
-  name: janitor cyborg head
-  components:
-    - type: Sprite
-      state: janitor_head
-    - type: Icon
-      state: janitor_head
-    - type: Tag
-      tags:
-        - Trash
-        - BorgHead
-        - BorgJanitorHead
-
-- type: entity
-  id: TorsoBorgJanitor
-  parent: BaseBorgTorso
-  name: janitor cyborg torso
-  components:
-    - type: Sprite
-      state: janitor_chest
-    - type: Icon
-      state: janitor_chest
-    - type: Tag
-      tags:
-        - Trash
-        - BorgJanitorTorso
-
-# medical parts
-- type: entity
-  id: LeftArmBorgMedical
-  parent: BaseBorgArmLeft
-  name: medical cyborg left arm
-  components:
-    - type: Sprite
-      state: medical_l_arm
-    - type: Icon
-      state: medical_l_arm
-    - type: Tag
-      tags:
-        - Trash
-        - BorgArm
-        - BorgMedicalLArm
-
-- type: entity
-  id: RightArmBorgMedical
-  parent: BaseBorgArmRight
-  name: medical cyborg right arm
-  components:
-    - type: Sprite
-      state: medical_r_arm
-    - type: Icon
-      state: medical_r_arm
-    - type: Tag
-      tags:
-        - Trash
-        - BorgArm
-        - BorgMedicalRArm
-
-- type: entity
-  id: LeftLegBorgMedical
-  parent: BaseBorgLegLeft
-  name: medical cyborg left leg
-  components:
-    - type: Sprite
-      state: medical_l_leg
-    - type: Icon
-      state: medical_l_leg
-    - type: Tag
-      tags:
-        - Trash
-        - BorgLeg
-        - BorgMedicalLLeg
-
-- type: entity
-  id: RightLegBorgMedical
-  parent: BaseBorgLegRight
-  name: medical cyborg right leg
-  components:
-    - type: Sprite
-      state: medical_r_leg
-    - type: Icon
-      state: medical_r_leg
-    - type: Tag
-      tags:
-        - Trash
-        - BorgLeg
-        - BorgMedicalRLeg
-
-- type: entity
-  id: HeadBorgMedical
-  parent: BaseBorgHead
-  name: medical cyborg head
-  components:
-    - type: Sprite
-      state: medical_head
-    - type: Icon
-      state: medical_head
-    - type: Tag
-      tags:
-        - Trash
-        - BorgHead
-        - BorgMedicalHead
-
-- type: entity
-  id: TorsoBorgMedical
-  parent: BaseBorgTorso
-  name: medical cyborg torso
-  components:
-    - type: Sprite
-      state: medical_chest
-    - type: Icon
-      state: medical_chest
-    - type: Tag
-      tags:
-        - Trash
-        - BorgMedicalTorso
-
-# mining parts
-- type: entity
-  id: LeftArmBorgMining
-  parent: BaseBorgArmLeft
-  name: mining cyborg left arm
-  components:
-    - type: Sprite
-      state: mining_l_arm
-    - type: Icon
-      state: mining_l_arm
-    - type: Tag
-      tags:
-        - Trash
-        - BorgArm
-        - BorgMiningLArm
-
-- type: entity
-  id: RightArmBorgMining
-  parent: BaseBorgArmRight
-  name: mining cyborg right arm
-  components:
-    - type: Sprite
-      state: mining_r_arm
-    - type: Icon
-      state: mining_r_arm
-    - type: Tag
-      tags:
-        - Trash
-        - BorgArm
-        - BorgMiningRArm
-
-- type: entity
-  id: LeftLegBorgMining
-  parent: BaseBorgLegLeft
-  name: mining cyborg left leg
-  components:
-    - type: Sprite
-      state: mining_l_leg
-    - type: Icon
-      state: mining_l_leg
-    - type: Tag
-      tags:
-        - Trash
-        - BorgLeg
-        - BorgMiningLLeg
-
-- type: entity
-  id: RightLegBorgMining
-  parent: BaseBorgLegRight
-  name: mining cyborg right leg
-  components:
-    - type: Sprite
-      state: mining_r_leg
-    - type: Icon
-      state: mining_r_leg
-    - type: Tag
-      tags:
-        - Trash
-        - BorgLeg
-        - BorgMiningRLeg
-
-- type: entity
-  id: HeadBorgMining
-  parent: BaseBorgHead
-  name: mining cyborg head
-  components:
-    - type: Sprite
-      state: mining_head
-    - type: Icon
-      state: mining_head
-    - type: Tag
-      tags:
-        - Trash
-        - BorgHead
-        - BorgMiningHead
-
-- type: entity
-  id: TorsoBorgMining
-  parent: BaseBorgTorso
-  name: mining cyborg torso
-  components:
-    - type: Sprite
-      state: mining_chest
-    - type: Icon
-      state: mining_chest
-    - type: Tag
-      tags:
-        - Trash
-        - BorgMiningTorso
-
-# service parts
-- type: entity
-  id: LeftArmBorgService
-  parent: BaseBorgArmLeft
-  name: service cyborg left arm
-  components:
-  - type: Sprite
-    state: service_l_arm
-  - type: Icon
-    state: service_l_arm
-  - type: Tag
-    tags:
-    - Trash
-    - BorgArm
-    - BorgServiceLArm
-
-- type: entity
-  id: RightArmBorgService
-  parent: BaseBorgArmRight
-  name: service cyborg right arm
-  components:
-  - type: Sprite
-    state: service_r_arm
-  - type: Icon
-    state: service_r_arm
-  - type: Tag
-    tags:
-    - Trash
-    - BorgArm
-    - BorgServiceRArm
-
-- type: entity
-  id: LeftLegBorgService
-  parent: BaseBorgLegLeft
-  name: service cyborg left leg
-  components:
-  - type: Sprite
-    state: service_l_leg
-  - type: Icon
-    state: service_l_leg
-  - type: Tag
-    tags:
-    - Trash
-    - BorgLeg
-    - BorgServiceLLeg
-
-- type: entity
-  id: RightLegBorgService
-  parent: BaseBorgLegRight
-  name: service cyborg right leg
-  components:
-  - type: Sprite
-    state: service_r_leg
-  - type: Icon
-    state: service_r_leg
-  - type: Tag
-    tags:
-    - Trash
-    - BorgLeg
-    - BorgServiceRLeg
-
-- type: entity
-  id: HeadBorgService
-  parent: BaseBorgHead
-  name: service cyborg head
-  components:
-  - type: Sprite
-    state: service_head
-  - type: Icon
-    state: service_head
-  - type: Tag
-    tags:
-    - Trash
-    - BorgHead
-    - BorgServiceHead
-
-- type: entity
-  id: TorsoBorgService
-  parent: BaseBorgTorso
-  name: service cyborg torso
-  components:
-  - type: Sprite
-    state: service_chest
-  - type: Icon
-    state: service_chest
-  - type: Tag
-    tags:
-    - Trash
-    - BorgServiceTorso
index 9261e06ea2a78b2e9a4cf91975b0e2a30d684985..6afc06a79675167de92b4f21558bfb714ae173dc 100644 (file)
       borg_l_arm+o:
         whitelist:
           tags:
-          - BorgGenericLArm
+          - BorgLArm
       borg_r_arm+o:
         whitelist:
           tags:
-          - BorgGenericRArm
+          - BorgRArm
       borg_l_leg+o:
         whitelist:
           tags:
-          - BorgGenericLLeg
+          - BorgLLeg
       borg_r_leg+o:
         whitelist:
           tags:
-          - BorgGenericRLeg
+          - BorgRLeg
       borg_head+o:
         whitelist:
           tags:
-          - BorgGenericHead
+          - BorgHead
       borg_chest+o:
         whitelist:
           tags:
-          - BorgGenericTorso
-      service_l_arm+o:
-        whitelist:
-          tags:
-          - BorgServiceLArm
-      service_r_arm+o:
-        whitelist:
-          tags:
-          - BorgServiceRArm
-      service_l_leg+o:
-        whitelist:
-          tags:
-          - BorgServiceLLeg
-      service_r_leg+o:
-        whitelist:
-          tags:
-          - BorgServiceRLeg
-      service_head+o:
-        whitelist:
-          tags:
-          - BorgServiceHead
-      service_chest+o:
-        whitelist:
-          tags:
-          - BorgServiceTorso
-      engineer_l_arm+o:
-        whitelist:
-          tags:
-          - BorgEngineerLArm
-      engineer_r_arm+o:
-        whitelist:
-          tags:
-          - BorgEngineerRArm
-      engineer_l_leg+o:
-        whitelist:
-          tags:
-          - BorgEngineerLLeg
-      engineer_r_leg+o:
-        whitelist:
-          tags:
-          - BorgEngineerRLeg
-      engineer_head+o:
-        whitelist:
-          tags:
-          - BorgEngineerHead
-      engineer_chest+o:
-        whitelist:
-          tags:
-          - BorgEngineerTorso
-      mining_l_arm+o:
-        whitelist:
-          tags:
-            - BorgMiningLArm
-      mining_r_arm+o:
-        whitelist:
-          tags:
-            - BorgMiningRArm
-      mining_l_leg+o:
-        whitelist:
-          tags:
-            - BorgMiningLLeg
-      mining_r_leg+o:
-        whitelist:
-          tags:
-            - BorgMiningRLeg
-      mining_head+o:
-        whitelist:
-          tags:
-            - BorgMiningHead
-      mining_chest+o:
-        whitelist:
-          tags:
-            - BorgMiningTorso
-      medical_l_arm+o:
-        whitelist:
-          tags:
-            - BorgMedicalLArm
-      medical_r_arm+o:
-        whitelist:
-          tags:
-            - BorgMedicalRArm
-      medical_l_leg+o:
-        whitelist:
-          tags:
-            - BorgMedicalLLeg
-      medical_r_leg+o:
-        whitelist:
-          tags:
-            - BorgMedicalRLeg
-      medical_head+o:
-        whitelist:
-          tags:
-            - BorgMedicalHead
-      medical_chest+o:
-        whitelist:
-          tags:
-            - BorgMedicalTorso
-      janitor_l_leg+o:
-        whitelist:
-          tags:
-            - BorgJanitorLLeg
-      janitor_r_leg+o:
-        whitelist:
-          tags:
-            - BorgJanitorRLeg
-      janitor_head+o:
-        whitelist:
-          tags:
-            - BorgJanitorHead
-      janitor_chest+o:
-        whitelist:
-          tags:
-            - BorgJanitorTorso
+          - BorgTorso
   - type: ContainerContainer
     containers:
       part-container: !type:Container
   - type: PartAssembly
     parts:
       generic:
-      - BorgGenericLArm
-      - BorgGenericRArm
-      - BorgGenericLLeg
-      - BorgGenericRLeg
-      - BorgGenericHead
-      - BorgGenericTorso
-      service:
-      - BorgServiceLArm
-      - BorgServiceRArm
-      - BorgServiceLLeg
-      - BorgServiceRLeg
-      - BorgServiceHead
-      - BorgServiceTorso
-      engineer:
-      - BorgEngineerLArm
-      - BorgEngineerRArm
-      - BorgEngineerLLeg
-      - BorgEngineerRLeg
-      - BorgEngineerHead
-      - BorgEngineerTorso
-      medical:
-      - BorgMedicalLArm
-      - BorgMedicalRArm
-      - BorgMedicalLLeg
-      - BorgMedicalRLeg
-      - BorgMedicalHead
-      - BorgMedicalTorso
-      janitor:
-      - BorgJanitorLLeg
-      - BorgJanitorRLeg
-      - BorgJanitorHead
-      - BorgJanitorTorso
-      mining:
-      - BorgMiningLArm
-      - BorgMiningRArm
-      - BorgMiningLLeg
-      - BorgMiningRLeg
-      - BorgMiningHead
-      - BorgMiningTorso
+      - BorgLArm
+      - BorgRArm
+      - BorgLLeg
+      - BorgRLeg
+      - BorgHead
+      - BorgTorso
   - type: Construction
     graph: Cyborg
     node: start
index 020566ad1a73eead613581825376a8f4e4699cd4..6a94891d127bc787a79bc051bc2d9d886c6d7a37 100644 (file)
     - BorgModuleFireExtinguisher
     - BorgModuleRadiationDetection
     - BorgModuleTool
-    - BorgModuleAppraisal
-    - BorgModuleConstruction
-    - BorgModuleService
-    - BorgModuleTreatment
-    - BorgModuleCleaning
     - CyborgEndoskeleton
     - LeftArmBorg
     - RightArmBorg
     - RightLegBorg
     - LightHeadBorg
     - TorsoBorg
-    - LeftArmBorgEngineer
-    - RightArmBorgEngineer
-    - LeftLegBorgEngineer
-    - RightLegBorgEngineer
-    - HeadBorgEngineer
-    - TorsoBorgEngineer
-    - LeftLegBorgJanitor
-    - RightLegBorgJanitor
-    - HeadBorgJanitor
-    - TorsoBorgJanitor
-    - LeftArmBorgMedical
-    - RightArmBorgMedical
-    - LeftLegBorgMedical
-    - RightLegBorgMedical
-    - HeadBorgMedical
-    - TorsoBorgMedical
-    - LeftArmBorgMining
-    - RightArmBorgMining
-    - LeftLegBorgMining
-    - RightLegBorgMining
-    - HeadBorgMining
-    - TorsoBorgMining
-    - LeftArmBorgService
-    - RightArmBorgService
-    - LeftLegBorgService
-    - RightLegBorgService
-    - HeadBorgService
-    - TorsoBorgService
     dynamicRecipes:
     - ProximitySensor
-    - BorgModuleLightReplacer
     - BorgModuleAdvancedCleaning
-    - BorgModuleMining
-    - BorgModuleGrapplingGun
     - BorgModuleAdvancedTool
     - BorgModuleGPS
-    - BorgModuleRCD
     - BorgModuleArtifact
     - BorgModuleAnomaly
     - BorgModuleGardening
     - BorgModuleHarvesting
-    - BorgModuleMusique
-    - BorgModuleClowning
-    - BorgModuleDiagnosis
     - BorgModuleDefibrillator
     - BorgModuleAdvancedTreatment
     - RipleyHarness
index d43519f61cf4f17c3080cfa59a632464b57ca353..3d3ef29eb0371f8c611ece6f0c8a4c72c72df699 100644 (file)
@@ -26,8 +26,7 @@
   - name: head
     slotTexture: head
     slotFlags: HEAD
-    slotGroup: MainHotbar
-    uiWindowPos: 0,0
+    uiWindowPos: 1,0
     strippingWindowPos: 0,0
     displayName: Head
     offset: 0.015625, 0
index 0f012cefc98e5feb8f590feea7341083b3a60b01..4ebc43667c5e9c817bb9c7182fff77b0b55899ef 100644 (file)
@@ -5,18 +5,6 @@
   - node: start
     entity: CyborgEndoskeleton
     edges:
-
-    # empty the parts via prying
-    - to: start
-      conditions:
-      - !type:ContainerNotEmpty
-        container: part-container
-      steps:
-      - tool: Prying
-        doAfter: 0.5
-        completed:
-          - !type:EmptyAllContainers
-
     - to: cyborg
       steps:
       - assemblyId: generic
 
       - tool: Screwing
         doAfter: 0.5
-    
-    - to: engineer
-      steps:
-      - assemblyId: engineer
-        guideString: borg-construction-guide-string
-
-      - material: Cable
-        amount: 1
-        doAfter: 1
-        store: part-container
-
-      - component: Flash
-        name: flash
-        store: part-container
-        icon:
-          sprite: Objects/Weapons/Melee/flash.rsi
-          state: flash
-
-      - component: Flash
-        name: second flash
-        store: part-container
-        icon:
-          sprite: Objects/Weapons/Melee/flash.rsi
-          state: flash
-
-      - tool: Screwing
-        doAfter: 0.5
-
-    - to: janitor
-      steps:
-      - assemblyId: janitor
-        guideString: borg-construction-guide-string
-
-      - material: Cable
-        amount: 1
-        doAfter: 1
-        store: part-container
-
-      - component: Flash
-        name: flash
-        store: part-container
-        icon:
-          sprite: Objects/Weapons/Melee/flash.rsi
-          state: flash
-
-      - component: Flash
-        name: second flash
-        store: part-container
-        icon:
-          sprite: Objects/Weapons/Melee/flash.rsi
-          state: flash
-
-      - tool: Screwing
-        doAfter: 0.5
-
-    - to: medical
-      steps:
-      - assemblyId: medical
-        guideString: borg-construction-guide-string
-
-      - material: Cable
-        amount: 1
-        doAfter: 1
-        store: part-container
-
-      - component: Flash
-        name: flash
-        store: part-container
-        icon:
-          sprite: Objects/Weapons/Melee/flash.rsi
-          state: flash
-
-      - component: Flash
-        name: second flash
-        store: part-container
-        icon:
-          sprite: Objects/Weapons/Melee/flash.rsi
-          state: flash
-
-      - tool: Screwing
-        doAfter: 0.5
-
-    - to: mining
-      steps:
-      - assemblyId: mining
-        guideString: borg-construction-guide-string
-
-      - material: Cable
-        amount: 1
-        doAfter: 1
-        store: part-container
-
-      - component: Flash
-        name: flash
-        store: part-container
-        icon:
-          sprite: Objects/Weapons/Melee/flash.rsi
-          state: flash
-
-      - component: Flash
-        name: second flash
-        store: part-container
-        icon:
-          sprite: Objects/Weapons/Melee/flash.rsi
-          state: flash
-
-      - tool: Screwing
-        doAfter: 0.5
-
-    - to: service
-      steps:
-      - assemblyId: service
-        guideString: borg-construction-guide-string
-
-      - material: Cable
-        amount: 1
-        doAfter: 1
-        store: part-container
-
-      - component: Flash
-        name: flash
-        store: part-container
-        icon:
-          sprite: Objects/Weapons/Melee/flash.rsi
-          state: flash
-
-      - component: Flash
-        name: second flash
-        store: part-container
-        icon:
-          sprite: Objects/Weapons/Melee/flash.rsi
-          state: flash
-
-      - tool: Screwing
-        doAfter: 0.5
 
   - node: cyborg
-    entity: BorgChassisGeneric
-
-  - node: engineer
-    entity: BorgChassisEngineer
-
-  - node: janitor
-    entity: BorgChassisJanitor
-
-  - node: mining
-    entity: BorgChassisMining
-
-  - node: medical
-    entity: BorgChassisMedical
-
-  - node: service
-    entity: BorgChassisService
-
-  - node: syndicateassault
-    entity: BorgChassisSyndicateAssault
-
-  - node: syndicatemedical
-    entity: BorgChassisSyndicateMedical
-
-  - node: syndicatesaboteur
-    entity: BorgChassisSyndicateSaboteur
+    entity: BorgChassisSelectable
index bf8deba9840e55e490349d14c622de3e1e4f68ee..a4413e01ebee1173986771d5475eafae0bb0ea4c 100644 (file)
@@ -61,8 +61,6 @@
   materials:
     Steel: 1500
 
-# Generic
-
 - type: latheRecipe
   parent: BaseBorgLimbRecipe
   id: LeftArmBorg
   id: TorsoBorg
   result: TorsoBorg
 
-# Engineer
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: LeftArmBorgEngineer
-  result: LeftArmBorgEngineer
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: RightArmBorgEngineer
-  result: RightArmBorgEngineer
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: LeftLegBorgEngineer
-  result: LeftLegBorgEngineer
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: RightLegBorgEngineer
-  result: RightLegBorgEngineer
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: HeadBorgEngineer
-  result: HeadBorgEngineer
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: TorsoBorgEngineer
-  result: TorsoBorgEngineer
-
-# Medical
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: LeftArmBorgMedical
-  result: LeftArmBorgMedical
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: RightArmBorgMedical
-  result: RightArmBorgMedical
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: LeftLegBorgMedical
-  result: LeftLegBorgMedical
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: RightLegBorgMedical
-  result: RightLegBorgMedical
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: HeadBorgMedical
-  result: HeadBorgMedical
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: TorsoBorgMedical
-  result: TorsoBorgMedical
-
-# Mining
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: LeftArmBorgMining
-  result: LeftArmBorgMining
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: RightArmBorgMining
-  result: RightArmBorgMining
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: LeftLegBorgMining
-  result: LeftLegBorgMining
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: RightLegBorgMining
-  result: RightLegBorgMining
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: HeadBorgMining
-  result: HeadBorgMining
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: TorsoBorgMining
-  result: TorsoBorgMining
-
-# Service
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: LeftArmBorgService
-  result: LeftArmBorgService
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: RightArmBorgService
-  result: RightArmBorgService
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: LeftLegBorgService
-  result: LeftLegBorgService
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: RightLegBorgService
-  result: RightLegBorgService
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: HeadBorgService
-  result: HeadBorgService
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: TorsoBorgService
-  result: TorsoBorgService
-
-# Janitor
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: LeftLegBorgJanitor
-  result: LeftLegBorgJanitor
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: RightLegBorgJanitor
-  result: RightLegBorgJanitor
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: HeadBorgJanitor
-  result: HeadBorgJanitor
-  materials:
-    Steel: 500
-    Glass: 200
-
-- type: latheRecipe
-  parent: BaseBorgLimbRecipe
-  id: TorsoBorgJanitor
-  result: TorsoBorgJanitor
-  materials:
-    Steel: 500
-    Glass: 200
-
 # Parts
 
 - type: latheRecipe
   id: BorgModuleTool
   result: BorgModuleTool
 
-# Mining Modules
-
-- type: latheRecipe
-  parent: BaseBorgModuleRecipe
-  id: BorgModuleAppraisal
-  result: BorgModuleAppraisal
-
-- type: latheRecipe
-  parent: BaseBorgModuleRecipe
-  id: BorgModuleMining
-  result: BorgModuleMining
-
-- type: latheRecipe
-  parent: BaseGoldBorgModuleRecipe
-  id: BorgModuleGrapplingGun
-  result: BorgModuleGrapplingGun
-
 # Engineering Modules
 
 - type: latheRecipe
   id: BorgModuleAdvancedTool
   result: BorgModuleAdvancedTool
 
-- type: latheRecipe
-  parent: BaseBorgModuleRecipe
-  id: BorgModuleConstruction
-  result: BorgModuleConstruction
-
-- type: latheRecipe
-  parent: BaseGoldBorgModuleRecipe
-  id: BorgModuleRCD
-  result: BorgModuleRCD
-
 # Janitor Modules
 
-- type: latheRecipe
-  parent: BaseBorgModuleRecipe
-  id: BorgModuleLightReplacer
-  result: BorgModuleLightReplacer
-
-- type: latheRecipe
-  parent: BaseBorgModuleRecipe
-  id: BorgModuleCleaning
-  result: BorgModuleCleaning
-
 - type: latheRecipe
   parent: BaseGoldBorgModuleRecipe
   id: BorgModuleAdvancedCleaning
 
 # Medical Modules
 
-- type: latheRecipe
-  parent: BaseBorgModuleRecipe
-  id: BorgModuleDiagnosis
-  result: BorgModuleDiagnosis
-
-- type: latheRecipe
-  parent: BaseBorgModuleRecipe
-  id: BorgModuleTreatment
-  result: BorgModuleTreatment
-
 - type: latheRecipe
   parent: BaseGoldBorgModuleRecipe
   id: BorgModuleAdvancedTreatment
 
 # Service Modules
 
-- type: latheRecipe
-  parent: BaseBorgModuleRecipe
-  id: BorgModuleService
-  result: BorgModuleService
-
-- type: latheRecipe
-  parent: BaseBorgModuleRecipe
-  id: BorgModuleMusique
-  result: BorgModuleMusique
-
 - type: latheRecipe
   parent: BaseBorgModuleRecipe
   id: BorgModuleGardening
   parent: BaseBorgModuleRecipe
   id: BorgModuleHarvesting
   result: BorgModuleHarvesting
-
-- type: latheRecipe
-  parent: BaseBorgModuleRecipe
-  id: BorgModuleClowning
-  result: BorgModuleClowning
index b990eb6ae40ccb5fd5213b0fd2ff9d046b9c6c50..9430c391a99213264512c5faf1e2f19b490902dc 100644 (file)
@@ -66,8 +66,6 @@
   recipeUnlocks:
     - ComputerTelevisionCircuitboard
     - SynthesizerInstrument
-    - BorgModuleMusique
-    - BorgModuleClowning
     - DawInstrumentMachineCircuitboard
     - MassMediaCircuitboard
     - JukeboxCircuitBoard
@@ -82,7 +80,6 @@
   tier: 1
   cost: 5000
   recipeUnlocks:
-  - BorgModuleLightReplacer
   - BorgModuleAdvancedCleaning
 
 - type: technology
index e65c734ffdab6b956662a1480f5f8706a4bf422b..817e50834b7044886e7cbc5b0a0ed6991abc0029 100644 (file)
@@ -12,8 +12,6 @@
   recipeUnlocks:
   - MiningDrill
   - MineralScannerEmpty
-  - BorgModuleMining
-  - BorgModuleGrapplingGun
   - OreProcessorIndustrialMachineCircuitboard
   - ClothingMaskWeldingGas
 
     - PowerDrill
     - JawsOfLife
     - BorgModuleAdvancedTool
-    - BorgModuleRCD
 
 - type: technology
   id: MassExcavation
index 4cbede17ca2dc90186963147e9cf38766fe29753..c62482d286eef7d544f655e0e457afbf381b923e 100644 (file)
@@ -25,5 +25,5 @@
   canBeAntag: false
   icon: JobIconBorg
   supervisors: job-supervisors-rd
-  jobEntity: PlayerBorgGeneric
+  jobEntity: PlayerBorgBattery
   applyTraits: false
diff --git a/Resources/Prototypes/borg_types.yml b/Resources/Prototypes/borg_types.yml
new file mode 100644 (file)
index 0000000..f6294be
--- /dev/null
@@ -0,0 +1,218 @@
+# Generic borg
+- type: borgType
+  id: generic
+
+  # Description
+  dummyPrototype: BorgChassisGeneric
+
+  # Functional
+  extraModuleCount: 5
+  moduleWhitelist:
+    tags:
+    - BorgModuleGeneric
+
+  defaultModules:
+  - BorgModuleTool
+
+  radioChannels:
+  - Science
+
+  # Visual
+  inventoryTemplateId: borgShort
+  spriteBodyState: robot
+  spriteHasMindState: robot_e
+  spriteNoMindState: robot_e_r
+  spriteToggleLightState: robot_l
+
+  # Pet
+  petSuccessString: petting-success-generic-cyborg
+  petFailureString: petting-failure-generic-cyborg
+
+
+# Engineering borg
+- type: borgType
+  id: engineering
+
+  # Description
+  dummyPrototype: BorgChassisEngineer
+
+  # Functional
+  extraModuleCount: 3
+  moduleWhitelist:
+    tags:
+    - BorgModuleGeneric
+    - BorgModuleEngineering
+
+  defaultModules:
+  - BorgModuleTool
+  - BorgModuleConstruction
+  - BorgModuleRCD
+  - BorgModuleCable
+
+  radioChannels:
+  - Engineering
+  - Science
+
+  # Visual
+  inventoryTemplateId: borgShort
+  spriteBodyState: engineer
+  spriteHasMindState: engineer_e
+  spriteNoMindState: engineer_e_r
+  spriteToggleLightState: engineer_l
+
+  # Pet
+  petSuccessString: petting-success-engineer-cyborg
+  petFailureString: petting-failure-engineer-cyborg
+
+
+# Salvage borg
+- type: borgType
+  id: mining
+
+  # Description
+  dummyPrototype: BorgChassisMining
+
+  # Functional
+  extraModuleCount: 3
+  moduleWhitelist:
+    tags:
+    - BorgModuleGeneric
+    - BorgModuleCargo
+
+  defaultModules:
+  - BorgModuleGrapplingGun
+  - BorgModuleMining
+  - BorgModuleAppraisal
+
+  radioChannels:
+  - Supply
+  - Science
+
+  # Visual
+  inventoryTemplateId: borgTall
+  spriteBodyState: miner
+  spriteBodyMovementState: miner_moving
+  spriteHasMindState: miner_e
+  spriteNoMindState: miner_e_r
+  spriteToggleLightState: miner_l
+
+  # Pet
+  petSuccessString: petting-success-salvage-cyborg
+  petFailureString: petting-failure-salvage-cyborg
+
+
+# Janitor borg
+- type: borgType
+  id: janitor
+
+  # Description
+  dummyPrototype: BorgChassisJanitor
+
+  # Functional
+  extraModuleCount: 3
+  moduleWhitelist:
+    tags:
+    - BorgModuleGeneric
+    - BorgModuleJanitor
+
+  defaultModules:
+  - BorgModuleLightReplacer
+  - BorgModuleCleaning
+
+  radioChannels:
+  - Science
+  - Service
+
+  # Visual
+  inventoryTemplateId: borgShort
+  spriteBodyState: janitor
+  spriteBodyMovementState: janitor_moving
+  spriteHasMindState: janitor_e
+  spriteNoMindState: janitor_e_r
+  spriteToggleLightState: janitor_l
+
+  # Pet
+  petSuccessString: petting-success-janitor-cyborg
+  petFailureString: petting-failure-janitor-cyborg
+
+
+# Medical borg
+- type: borgType
+  id: medical
+
+  # Description
+  dummyPrototype: BorgChassisMedical
+
+  # Functional
+  extraModuleCount: 3
+  moduleWhitelist:
+    tags:
+    - BorgModuleGeneric
+    - BorgModuleMedical
+
+  defaultModules:
+  - BorgModuleTreatment
+
+  radioChannels:
+  - Science
+  - Medical
+
+  addComponents:
+  - type: SolutionScanner
+  - type: ShowHealthBars
+    damageContainers:
+    - Biological
+  - type: ShowHealthIcons
+    damageContainers:
+    - Biological
+
+  # Visual
+  inventoryTemplateId: borgDutch
+  spriteBodyState: medical
+  spriteBodyMovementState: medical_moving
+  spriteHasMindState: medical_e
+  spriteNoMindState: medical_e_r
+  spriteToggleLightState: medical_l
+
+  # Pet
+  petSuccessString: petting-success-medical-cyborg
+  petFailureString: petting-failure-medical-cyborg
+
+  # Sounds
+  footstepCollection:
+    collection: FootstepHoverBorg
+
+
+# Service borg
+- type: borgType
+  id: service
+
+  # Description
+  dummyPrototype: BorgChassisService
+
+  # Functional
+  extraModuleCount: 3
+  moduleWhitelist:
+    tags:
+    - BorgModuleGeneric
+    - BorgModuleService
+
+  defaultModules:
+  - BorgModuleMusique
+  - BorgModuleService
+  - BorgModuleClowning
+
+  radioChannels:
+  - Science
+  - Service
+
+  # Visual
+  inventoryTemplateId: borgTall
+  spriteBodyState: service
+  spriteHasMindState: service_e
+  spriteNoMindState: service_e_r
+  spriteToggleLightState: service_l
+
+  # Pet
+  petSuccessString: petting-success-service-cyborg
+  petFailureString: petting-failure-service-cyborg
index be9c90ce93d8435a2f17d4b899502acca3df38a0..6112f93c16486a2ce7c658c5ac3ec30c6a13c947 100644 (file)
 - type: Tag
   id: BorgArm
 
-- type: Tag
-  id: BorgEngineerHead
-
-- type: Tag
-  id: BorgEngineerLArm
-
-- type: Tag
-  id: BorgEngineerLLeg
-
-- type: Tag
-  id: BorgEngineerRArm
-
-- type: Tag
-  id: BorgEngineerRLeg
-
-- type: Tag
-  id: BorgEngineerTorso
-
-- type: Tag
-  id: BorgGenericHead
-
-- type: Tag
-  id: BorgGenericLArm
-
-- type: Tag
-  id: BorgGenericLLeg
-
-- type: Tag
-  id: BorgGenericRArm
-
-- type: Tag
-  id: BorgGenericRLeg
-
-- type: Tag
-  id: BorgGenericTorso
-
 - type: Tag
   id: BorgHead
 
 - type: Tag
-  id: BorgJanitorHead
-
-- type: Tag
-  id: BorgJanitorLLeg
-
-- type: Tag
-  id: BorgJanitorRLeg
-
-- type: Tag
-  id: BorgJanitorTorso
+  id: BorgLArm
 
 - type: Tag
-  id: BorgLeg
-
-- type: Tag
-  id: BorgMedicalHead
-
-- type: Tag
-  id: BorgMedicalLArm
-
-- type: Tag
-  id: BorgMedicalLLeg
-
-- type: Tag
-  id: BorgMedicalRArm
-
-- type: Tag
-  id: BorgMedicalRLeg
-
-- type: Tag
-  id: BorgMedicalTorso
-
-- type: Tag
-  id: BorgMiningHead
+  id: BorgLLeg
 
 - type: Tag
-  id: BorgMiningLArm
+  id: BorgRArm
 
 - type: Tag
-  id: BorgMiningLLeg
+  id: BorgRLeg
 
 - type: Tag
-  id: BorgMiningRArm
+  id: BorgTorso
 
 - type: Tag
-  id: BorgMiningRLeg
-
-- type: Tag
-  id: BorgMiningTorso
+  id: BorgLeg
 
 - type: Tag
   id: BorgModuleCargo
 - type: Tag
   id: BorgModuleSyndicateAssault
 
-- type: Tag
-  id: BorgServiceHead
-
-- type: Tag
-  id: BorgServiceLArm
-
-- type: Tag
-  id: BorgServiceLLeg
-
-- type: Tag
-  id: BorgServiceRArm
-
-- type: Tag
-  id: BorgServiceRLeg
-
-- type: Tag
-  id: BorgServiceTorso
-
 - type: Tag
   id: Bot
 
index 2b8defb0705845c4a38fa9b9d954d1827ae517f0..c1507ca53960a78bf4dab4d48564cb2f4f6133af 100644 (file)
   </Box>
   Both brains can be fabricated without requiring any additional research.
 
-  ## Chassis
-  While all cyborgs share the same endoskeleton, not all share the same chassis. The chassis determines what modules the cyborg can have, along with the [color=#a4885c]departmental radio channel[/color] they correspond to. By default, they will always have access to [color=#D381C9]Science[/color] and [color=green]station-wide[/color] frequencies, along with having [color=#a4885c]all-access[/color].
+  ## Cyborg types
+  Once created, a cyborg needs to specialize its chassis to a duty on the station. This determines what modules it starts with, which additional modules can be installed, and what [color=#a4885c]departmental radio channel[/color] it has access to. All cyborgs have access to the [color=#D381C9]Science[/color] and [color=green]station-wide[/color] radio channels. All cyborg types have [color=#a4885c]all-access[/color].
   <Box>
     <GuideEntityEmbed Entity="BorgChassisGeneric" Caption="Generic"/>
     <GuideEntityEmbed Entity="BorgChassisEngineer" Caption="Engineering"/>
-    <GuideEntityEmbed Entity="BorgChassisMining" Caption="Mining"/>
+    <GuideEntityEmbed Entity="BorgChassisMining" Caption="Salvage"/>
     <GuideEntityEmbed Entity="BorgChassisJanitor" Caption="Janitor"/>
     <GuideEntityEmbed Entity="BorgChassisService" Caption="Service"/>
     <GuideEntityEmbed Entity="BorgChassisMedical" Caption="Medical"/>
   </Box>
   <Box>
-    [italic]Examples of various cyborg chassis[/italic]
+    [italic]Examples of various cyborg types[/italic]
   </Box>
-  If you wish to change the chassis of an already existing cyborg, you have to construct a whole new one, limbs and frame included. The brain, power cell and modules [italic](if it can fit in the new chassis,)[/italic] can be carried over from the old chassis, if desired.
+  Once a cyborg chassis has been specialized, it cannot be changed. To change types, a new chassis must be constructed. The brain, power cell, and any modules [italic](if they are compatible with the new chassis)[/italic] can be carried over from the old chassis if desired.
 
   ## Modules
   <Box>
     <GuideEntityEmbed Entity="BorgModuleTool" Caption="Tool Cyborg Module"/>
   </Box>
-  A cyborg isn't able to do much without [color=#a4885c]modules[/color]. These printed circuit boards are specific to cyborgs and grant additional functionality to them. They are printed at the [color=#a4885c]Exosuit Fabricator[/color].
+  Cyborgs do not have hands, and therefore cannot pick things up like most other players. Instead, their equipment is provided by various [color=#a4885c]modules[/color]. Every cyborg type starts with its own specific set of modules, but additional modules can be inserted as upgrades. These additional modules can be printed at the [color=#a4885c]Exosuit Fabricator[/color].
 
-  [color=#a4885c]Generic[/color] modules add versatility. They can be fitted into any chassis, granting useful tools such as crowbars, GPS, and the ability to interact with cables. [bold]The generic borg chassis can fit up to 6 modules in total.[/bold]
+  [color=#a4885c]Generic[/color] modules add versatility. They can be fitted into any chassis, granting useful tools such as crowbars, GPS, and the ability to interact with cables. [bold]The generic cyborg chassis can fit up to five additional modules.[/bold]
   <Box>
     <GuideEntityEmbed Entity="BorgModuleCable" Caption="Cable"/>
     <GuideEntityEmbed Entity="BorgModuleGPS" Caption="GPS"/>
@@ -49,7 +49,7 @@
     [italic]Examples of generic modules[/italic]
   </Box>
 
-  For more specific needs, [color=#a4885c]specialized[/color] modules are available, granting capabilities like scanning anomalies, constructing walls, reviving crew mates, or cleaning a space lube spill. These modules are typically colored with the same palette as the department [italic](or occupation)[/italic] they relate to. These modules [italic](with exception to [color=#D381C9]science[/color] modules, which can fit any chassis,)[/italic] can only be fitted in their associated borg chassis. [bold]The specialized borg chassis, being the engineering, janitorial, service, medical, and mining chassis, can fit up to 4 modules.[/bold]
+  For more specific needs, [color=#a4885c]specialized[/color] modules are available, granting capabilities like scanning anomalies, constructing walls, reviving crew mates, or cleaning a space lube spill. These modules are typically colored with the same palette as the department [italic](or occupation)[/italic] they relate to. These modules [italic](with exception to [color=#D381C9]science[/color] modules, which can fit any chassis,)[/italic] can only be fitted in their associated borg chassis. [bold]The specialized borg chassis, being the engineering, janitorial, service, medical, and mining chassis, can fit up to three additional modules.[/bold]
   <Box>
     <GuideEntityEmbed Entity="BorgModuleAnomaly" Caption="Anomaly"/>
     <GuideEntityEmbed Entity="BorgModuleRCD" Caption="RCD"/>
index dc8a6fcf9c6e6923ffcfacd3f388a58e789ca7d3..2ebb6eddcf50b5a39a62c7db3c3d289d2fa76814 100644 (file)
@@ -48,7 +48,7 @@
         },
         {
             "name":"light-replacer-module"
-        }, 
+        },
         {
             "name":"cleaning-module"
         },
         },
         {
             "name":"syndicate-martyr-module"
+        },
+        {
+            "name": "select-type"
         }
     ]
 }
diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/select-type.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/select-type.png
new file mode 100644 (file)
index 0000000..766fd71
Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/select-type.png differ
index 3ed618dcfd70a1bb8ce3751e9efa943718e55b93..8940df2b65e15b4f8f570b6041e9c541a486e108 100644 (file)
@@ -440,3 +440,38 @@ BlueprintFlare: null
 
 # 2024-10-04
 BaseAdvancedPen: Pen
+
+# 2024-10-09
+# Removal of separate borg chassis parts, replace them with generic borg parts.
+LeftArmBorgEngineer: LeftArmBorg
+RightArmBorgEngineer: RightArmBorg
+LeftLegBorgEngineer: LeftLegBorg
+RightLegBorgEngineer: RightLegBorg
+HeadBorgEngineer: LightHeadBorg
+TorsoBorgEngineer: TorsoBorg
+
+LeftArmBorgMedical: LeftArmBorg
+RightArmBorgMedical: RightArmBorg
+LeftLegBorgMedical: LeftLegBorg
+RightLegBorgMedical: RightLegBorg
+HeadBorgMedical: LightHeadBorg
+TorsoBorgMedical: TorsoBorg
+
+LeftArmBorgMining: LeftArmBorg
+RightArmBorgMining: RightArmBorg
+LeftLegBorgMining: LeftLegBorg
+RightLegBorgMining: RightLegBorg
+HeadBorgMining: LightHeadBorg
+TorsoBorgMining: TorsoBorg
+
+LeftArmBorgService: LeftArmBorg
+RightArmBorgService: RightArmBorg
+LeftLegBorgService: LeftLegBorg
+RightLegBorgService: RightLegBorg
+HeadBorgService: LightHeadBorg
+TorsoBorgService: TorsoBorg
+
+LeftLegBorgJanitor: LeftLegBorg
+RightLegBorgJanitor: RightLegBorg
+HeadBorgJanitor: LightHeadBorg
+TorsoBorgJanitor: TorsoBorg