]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Chameleon controller implant (Clothing fast switch) (#33887)
authorbeck-thompson <107373427+beck-thompson@users.noreply.github.com>
Fri, 30 May 2025 10:07:25 +0000 (03:07 -0700)
committerGitHub <noreply@github.com>
Fri, 30 May 2025 10:07:25 +0000 (12:07 +0200)
* Add the chameleon controller implant

* address the issues (Git please dont kill me)

* Address the review and fix some merge conflicts!

* Cleanup

* Add use delay

* Silly mistakes

* Making a PR at 2 am: Gone wrong

* Predict use delay and disable the buttons until you can choose another

* First phase custom clothing

* Better system, now relays to agent id and mindshield. Chameleon loadouts are a lot better to work with as well

* Address the review! No more evil goto

* Slams way is better I should have read more closely xD

* Some of the jobs

* Add to Cargo, CentComm, Service, Passenger, Ninja, Cluwne, Wizard + Minor changes to existing; Add chameleon to bandanas, medals, jugsuits and HUDs

* Add everything else

* Fix test

* Job name

* This looks better

* Add department organization

* Minor cleanup

* Added some mindshields

* Remove redudent comment and change funcion name to be clearer

* Fix cluwne outfit

* fix merge conflicts

---------

Co-authored-by: SlamBamActionman <slambamactionman@gmail.com>
79 files changed:
Content.Client/Implants/ChameleonControllerSystem.cs [new file with mode: 0644]
Content.Client/Implants/UI/ChameleonControllerBoundUserInterface.cs [new file with mode: 0644]
Content.Client/Implants/UI/ChameleonControllerMenu.xaml [new file with mode: 0644]
Content.Client/Implants/UI/ChameleonControllerMenu.xaml.cs [new file with mode: 0644]
Content.IntegrationTests/Tests/Chameleon/ChameleonJobLoadoutTest.cs [new file with mode: 0644]
Content.Server/Access/Systems/AgentIDCardSystem.cs
Content.Server/Implants/ChameleonControllerSystem.cs [new file with mode: 0644]
Content.Server/PDA/PdaSystem.cs
Content.Shared/Access/Systems/SharedIdCardSystem.cs
Content.Shared/Implants/ChameleonControllerImplantComponent.cs [new file with mode: 0644]
Content.Shared/Implants/ChameleonOutfitPrototype.cs [new file with mode: 0644]
Content.Shared/Implants/SharedChameleonControllerSystem.cs [new file with mode: 0644]
Content.Shared/Inventory/InventorySystem.Relay.cs
Content.Shared/Mindshield/FakeMindShield/SharedFakeMindshieldSystem.cs
Content.Shared/Roles/Jobs/SharedJobSystem.cs
Content.Shared/Station/SharedStationSpawningSystem.cs
Resources/Locale/en-US/chameleon-outfits/chameleon-outfits.ftl [new file with mode: 0644]
Resources/Locale/en-US/implant/chameleon-controller.ftl [new file with mode: 0644]
Resources/Locale/en-US/job/department.ftl
Resources/Prototypes/Actions/types.yml
Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml
Resources/Prototypes/Entities/Clothing/Eyes/hud.yml
Resources/Prototypes/Entities/Clothing/Head/bandanas.yml
Resources/Prototypes/Entities/Clothing/Neck/medals.yml
Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml
Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml
Resources/Prototypes/Entities/Objects/Misc/implanters.yml
Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml
Resources/Prototypes/Loadouts/loadouts_chameleon.yml [new file with mode: 0644]
Resources/Prototypes/Roles/Antags/ninja.yml
Resources/Prototypes/Roles/Antags/nukeops.yml
Resources/Prototypes/Roles/Jobs/Cargo/cargo_technician.yml
Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml
Resources/Prototypes/Roles/Jobs/Cargo/salvage_specialist.yml
Resources/Prototypes/Roles/Jobs/CentComm/cburn.yml
Resources/Prototypes/Roles/Jobs/CentComm/deathsquad.yml
Resources/Prototypes/Roles/Jobs/CentComm/emergencyresponseteam.yml
Resources/Prototypes/Roles/Jobs/CentComm/official.yml
Resources/Prototypes/Roles/Jobs/Civilian/assistant.yml
Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml
Resources/Prototypes/Roles/Jobs/Civilian/botanist.yml
Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml
Resources/Prototypes/Roles/Jobs/Civilian/chef.yml
Resources/Prototypes/Roles/Jobs/Civilian/clown.yml
Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml
Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml
Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml
Resources/Prototypes/Roles/Jobs/Civilian/mime.yml
Resources/Prototypes/Roles/Jobs/Civilian/musician.yml
Resources/Prototypes/Roles/Jobs/Civilian/service_worker.yml
Resources/Prototypes/Roles/Jobs/Command/captain.yml
Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml
Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml
Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml
Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml
Resources/Prototypes/Roles/Jobs/Engineering/technical_assistant.yml
Resources/Prototypes/Roles/Jobs/Fun/cluwne.yml
Resources/Prototypes/Roles/Jobs/Fun/sus.yml [new file with mode: 0644]
Resources/Prototypes/Roles/Jobs/Fun/wizard_startinggear.yml
Resources/Prototypes/Roles/Jobs/Medical/chemist.yml
Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml
Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml
Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml
Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml
Resources/Prototypes/Roles/Jobs/Science/research_assistant.yml
Resources/Prototypes/Roles/Jobs/Science/research_director.yml
Resources/Prototypes/Roles/Jobs/Science/scientist.yml
Resources/Prototypes/Roles/Jobs/Security/detective.yml
Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml
Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml
Resources/Prototypes/Roles/Jobs/Security/security_officer.yml
Resources/Prototypes/Roles/Jobs/Security/warden.yml
Resources/Prototypes/Roles/Jobs/Wildcards/boxer.yml
Resources/Prototypes/Roles/Jobs/Wildcards/psychologist.yml
Resources/Prototypes/Roles/Jobs/Wildcards/reporter.yml
Resources/Prototypes/Roles/Jobs/Wildcards/zookeeper.yml
Resources/Prototypes/tags.yml
Resources/Textures/Actions/Implants/implants.rsi/chameleon.png [new file with mode: 0644]
Resources/Textures/Actions/Implants/implants.rsi/meta.json

diff --git a/Content.Client/Implants/ChameleonControllerSystem.cs b/Content.Client/Implants/ChameleonControllerSystem.cs
new file mode 100644 (file)
index 0000000..7db4b37
--- /dev/null
@@ -0,0 +1,5 @@
+using Content.Shared.Implants;
+
+namespace Content.Client.Implants;
+
+public sealed partial class ChameleonControllerSystem : SharedChameleonControllerSystem;
diff --git a/Content.Client/Implants/UI/ChameleonControllerBoundUserInterface.cs b/Content.Client/Implants/UI/ChameleonControllerBoundUserInterface.cs
new file mode 100644 (file)
index 0000000..42b891f
--- /dev/null
@@ -0,0 +1,49 @@
+using Content.Shared.Clothing;
+using Content.Shared.Implants;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Roles;
+using Content.Shared.Timing;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Implants.UI;
+
+[UsedImplicitly]
+public sealed class ChameleonControllerBoundUserInterface : BoundUserInterface
+{
+    private readonly UseDelaySystem _delay;
+
+    [ViewVariables]
+    private ChameleonControllerMenu? _menu;
+
+    public ChameleonControllerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+    {
+        _delay =  EntMan.System<UseDelaySystem>();
+    }
+
+    protected override void Open()
+    {
+        base.Open();
+
+        _menu = this.CreateWindow<ChameleonControllerMenu>();
+        _menu.OnJobSelected += OnJobSelected;
+    }
+
+    private void OnJobSelected(ProtoId<ChameleonOutfitPrototype> outfit)
+    {
+        if (!EntMan.TryGetComponent<UseDelayComponent>(Owner, out var useDelayComp))
+            return;
+
+        if (!_delay.TryResetDelay((Owner, useDelayComp), true))
+            return;
+
+        SendMessage(new ChameleonControllerSelectedOutfitMessage(outfit));
+
+        if (!_delay.TryGetDelayInfo((Owner, useDelayComp), out var delay) || _menu == null)
+            return;
+
+        _menu._lockedUntil = DateTime.Now.Add(delay.Length);
+        _menu.UpdateGrid(true);
+    }
+}
diff --git a/Content.Client/Implants/UI/ChameleonControllerMenu.xaml b/Content.Client/Implants/UI/ChameleonControllerMenu.xaml
new file mode 100644 (file)
index 0000000..39322a2
--- /dev/null
@@ -0,0 +1,12 @@
+<controls:FancyWindow xmlns="https://spacestation14.io"
+             xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+             Title="{Loc 'chameleon-controller-ui-window-name'}"
+             MinSize="250 300"
+             SetSize="850 700">
+    <BoxContainer Orientation="Vertical" Margin="7 0 0 0">
+        <ScrollContainer VerticalExpand="True">
+            <GridContainer Name="Grid" Columns="3" Margin="0 5" >
+            </GridContainer>
+        </ScrollContainer>
+    </BoxContainer>
+</controls:FancyWindow>
diff --git a/Content.Client/Implants/UI/ChameleonControllerMenu.xaml.cs b/Content.Client/Implants/UI/ChameleonControllerMenu.xaml.cs
new file mode 100644 (file)
index 0000000..a41e2e9
--- /dev/null
@@ -0,0 +1,157 @@
+using System.Linq;
+using System.Numerics;
+using Content.Client.Roles;
+using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Implants;
+using Content.Shared.StatusIcon;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Implants.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class ChameleonControllerMenu : FancyWindow
+{
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly IEntityManager _entityManager = default!;
+    private readonly SpriteSystem _sprite;
+    private readonly JobSystem _job;
+
+    // List of all the job protos that you can select!
+    private IEnumerable<ChameleonOutfitPrototype> _outfits;
+
+    // Lock the UI until this time
+    public DateTime? _lockedUntil;
+
+    private static readonly ProtoId<JobIconPrototype> UnknownIcon = "JobIconUnknown";
+    private static readonly LocId UnknownDepartment = "department-Unknown";
+
+    public event Action<ProtoId<ChameleonOutfitPrototype>>? OnJobSelected;
+
+    public ChameleonControllerMenu()
+    {
+        RobustXamlLoader.Load(this);
+        IoCManager.InjectDependencies(this);
+        _sprite = _entityManager.System<SpriteSystem>();
+        _job = _entityManager.System<JobSystem>();
+
+        _outfits = _prototypeManager.EnumeratePrototypes<ChameleonOutfitPrototype>();
+
+        UpdateGrid();
+    }
+
+    /// <summary>
+    ///     Fill the grid with the correct job icons and buttons.
+    /// </summary>
+    /// <param name="disabled">Set to true to disable all the buttons.</param>
+    public void UpdateGrid(bool disabled = false)
+    {
+        Grid.RemoveAllChildren();
+
+        // Dictionary to easily put outfits in departments.
+        // Department name -> UI element holding that department.
+        var departments = new Dictionary<string, BoxContainer>();
+
+        departments.Add(UnknownDepartment, CreateDepartment(UnknownDepartment));
+
+        // Go through every outfit and add them to the correct department.
+        foreach (var outfit in _outfits)
+        {
+            _prototypeManager.TryIndex(outfit.Job, out var jobProto);
+
+            var name = outfit.LoadoutName ?? outfit.Name ?? jobProto?.Name ?? "Prototype has no name or job.";
+
+            var jobIconId = outfit.Icon ?? jobProto?.Icon ?? UnknownIcon;
+            var jobIconProto = _prototypeManager.Index(jobIconId);
+
+            var outfitButton = CreateOutfitButton(disabled, name, jobIconProto, outfit.ID);
+
+            if (outfit.Job != null && _job.TryGetLowestWeightDepartment(outfit.Job, out var departmentPrototype))
+            {
+                if (!departments.ContainsKey(departmentPrototype.Name))
+                    departments.Add(departmentPrototype.Name, CreateDepartment(departmentPrototype.Name));
+
+                departments[departmentPrototype.Name].AddChild(outfitButton);
+            }
+            else
+            {
+                departments[UnknownDepartment].AddChild(outfitButton);
+            }
+        }
+
+        // Sort the departments by their weight.
+        var departmentList = departments.ToList();
+        departmentList.Sort((a, b) => a.Value.ChildCount.CompareTo(b.Value.ChildCount));
+
+        // Actually add the departments to the window.
+        foreach (var department in departmentList)
+        {
+            Grid.AddChild(department.Value);
+        }
+    }
+
+    private BoxContainer CreateDepartment(string name)
+    {
+        var departmentContainer = new BoxContainer
+        {
+            Orientation = BoxContainer.LayoutOrientation.Vertical,
+        };
+        departmentContainer.AddChild(new Label
+        {
+            Text = Loc.GetString(name),
+        });
+
+        return departmentContainer;
+    }
+
+    private BoxContainer CreateOutfitButton(bool disabled, string name, JobIconPrototype jobIconProto, ProtoId<ChameleonOutfitPrototype> outfitProto)
+    {
+        var outfitButton = new BoxContainer();
+
+        var button = new Button
+        {
+            HorizontalExpand = true,
+            StyleClasses = {StyleBase.ButtonSquare},
+            ToolTip = Loc.GetString(name),
+            Text = Loc.GetString(name),
+            Margin = new Thickness(0, 0, 15, 0),
+            Disabled = disabled,
+        };
+
+        var jobIconTexture = new TextureRect
+        {
+            Texture = _sprite.Frame0(jobIconProto.Icon),
+            TextureScale = new Vector2(2.5f, 2.5f),
+            Stretch = TextureRect.StretchMode.KeepCentered,
+            Margin = new Thickness(0, 0, 5, 0),
+        };
+
+        outfitButton.AddChild(jobIconTexture);
+        outfitButton.AddChild(button);
+
+        button.OnPressed += _ => JobButtonPressed(outfitProto);
+
+        return outfitButton;
+    }
+
+    private void JobButtonPressed(ProtoId<ChameleonOutfitPrototype> outfit)
+    {
+        OnJobSelected?.Invoke(outfit);
+    }
+
+    protected override void FrameUpdate(FrameEventArgs args)
+    {
+        base.FrameUpdate(args);
+
+        if (_lockedUntil == null || DateTime.Now < _lockedUntil)
+            return;
+
+        _lockedUntil = null;
+        UpdateGrid();
+    }
+}
diff --git a/Content.IntegrationTests/Tests/Chameleon/ChameleonJobLoadoutTest.cs b/Content.IntegrationTests/Tests/Chameleon/ChameleonJobLoadoutTest.cs
new file mode 100644 (file)
index 0000000..da061f0
--- /dev/null
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+using System.Text;
+using Content.Client.Implants;
+using Content.IntegrationTests.Tests.Interaction;
+using Content.Shared.Clothing;
+using Content.Shared.Implants;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Roles;
+using Robust.Shared.Prototypes;
+
+namespace Content.IntegrationTests.Tests.Chameleon;
+
+/// <summary>
+/// Ensures all round <see cref="IsProbablyRoundStartJob">"round start jobs"</see> have an associated chameleon loadout.
+/// </summary>
+public sealed class ChameleonJobLoadoutTest : InteractionTest
+{
+    private readonly List<ProtoId<JobPrototype>> JobBlacklist =
+    [
+
+    ];
+
+    [Test]
+    public async Task CheckAllJobs()
+    {
+        var alljobs = ProtoMan.EnumeratePrototypes<JobPrototype>();
+
+        // Job -> number of references
+        Dictionary<ProtoId<JobPrototype>, int> validJobs = new();
+
+        // Only add stuff that actually has clothing! We don't want stuff like AI or borgs.
+        foreach (var job in alljobs)
+        {
+            if (!IsProbablyRoundStartJob(job) || JobBlacklist.Contains(job.ID))
+                continue;
+
+            validJobs.Add(job.ID, 0);
+        }
+
+        var chameleons = ProtoMan.EnumeratePrototypes<ChameleonOutfitPrototype>();
+
+        foreach (var chameleon in chameleons)
+        {
+            if (chameleon.Job == null || !validJobs.ContainsKey(chameleon.Job.Value))
+                continue;
+
+            validJobs[chameleon.Job.Value] += 1;
+        }
+
+        var errorMessage = new StringBuilder();
+        errorMessage.AppendLine("The following job(s) have no chameleon prototype(s):");
+        var invalid = false;
+
+        // All round start jobs have a chameleon loadout
+        foreach (var job in validJobs)
+        {
+            if (job.Value != 0)
+                continue;
+
+            errorMessage.AppendLine(job.Key + " has no chameleonOutfit prototype.");
+            invalid = true;
+        }
+
+        if (!invalid)
+            return;
+
+        Assert.Fail(errorMessage.ToString());
+    }
+
+    /// <summary>
+    /// Best guess at what a "round start" job is.
+    /// </summary>
+    private bool IsProbablyRoundStartJob(JobPrototype job)
+    {
+        return job.StartingGear != null && ProtoMan.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID));
+    }
+
+}
index 9ede128a5a5e100c4de15e56a8463660ec0f0587..6385274336a1a29db0314082f670efa240b8c48a 100644 (file)
@@ -9,6 +9,11 @@ using Robust.Server.GameObjects;
 using Robust.Shared.Prototypes;
 using Content.Shared.Roles;
 using System.Diagnostics.CodeAnalysis;
+using Content.Server.Clothing.Systems;
+using Content.Server.Implants;
+using Content.Shared.Implants;
+using Content.Shared.Inventory;
+using Content.Shared.PDA;
 
 namespace Content.Server.Access.Systems
 {
@@ -18,6 +23,8 @@ namespace Content.Server.Access.Systems
         [Dependency] private readonly IdCardSystem _cardSystem = default!;
         [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
         [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+        [Dependency] private readonly ChameleonClothingSystem _chameleon = default!;
+        [Dependency] private readonly ChameleonControllerSystem _chamController = default!;
 
         public override void Initialize()
         {
@@ -28,6 +35,46 @@ namespace Content.Server.Access.Systems
             SubscribeLocalEvent<AgentIDCardComponent, AgentIDCardNameChangedMessage>(OnNameChanged);
             SubscribeLocalEvent<AgentIDCardComponent, AgentIDCardJobChangedMessage>(OnJobChanged);
             SubscribeLocalEvent<AgentIDCardComponent, AgentIDCardJobIconChangedMessage>(OnJobIconChanged);
+            SubscribeLocalEvent<AgentIDCardComponent, InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent>>(OnChameleonControllerOutfitChangedItem);
+        }
+
+        private void OnChameleonControllerOutfitChangedItem(Entity<AgentIDCardComponent> ent, ref InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent> args)
+        {
+            if (!TryComp<IdCardComponent>(ent, out var idCardComp))
+                return;
+
+            _prototypeManager.TryIndex(args.Args.ChameleonOutfit.Job, out var jobProto);
+
+            var jobIcon = args.Args.ChameleonOutfit.Icon ?? jobProto?.Icon;
+            var jobName = args.Args.ChameleonOutfit.Name ?? jobProto?.Name ?? "";
+
+            if (jobIcon != null)
+                _cardSystem.TryChangeJobIcon(ent, _prototypeManager.Index(jobIcon.Value), idCardComp);
+
+            if (jobName != "")
+                _cardSystem.TryChangeJobTitle(ent, Loc.GetString(jobName), idCardComp);
+
+            // If you have forced departments use those over the jobs actual departments.
+            if (args.Args.ChameleonOutfit?.Departments?.Count > 0)
+                _cardSystem.TryChangeJobDepartment(ent, args.Args.ChameleonOutfit.Departments, idCardComp);
+            else if (jobProto != null)
+                _cardSystem.TryChangeJobDepartment(ent, jobProto, idCardComp);
+
+            // Ensure that you chameleon IDs in PDAs correctly. Yes this is sus...
+
+            // There is one weird interaction: If the job / icon don't match the PDAs job the chameleon will be updated
+            // to the PDAs IDs sprite but the icon and job title will not match. There isn't a way to get around this
+            // really as there is no tie between job -> pda or pda -> job.
+
+            var idSlotGear = _chamController.GetGearForSlot(args, "id");
+            if (idSlotGear == null)
+                return;
+
+            var proto = _prototypeManager.Index(idSlotGear);
+            if (!proto.TryGetComponent<PdaComponent>(out var comp, EntityManager.ComponentFactory))
+                return;
+
+            _chameleon.SetSelectedPrototype(ent, comp.IdCard);
         }
 
         private void OnAfterInteract(EntityUid uid, AgentIDCardComponent component, AfterInteractEvent args)
diff --git a/Content.Server/Implants/ChameleonControllerSystem.cs b/Content.Server/Implants/ChameleonControllerSystem.cs
new file mode 100644 (file)
index 0000000..9e876f9
--- /dev/null
@@ -0,0 +1,151 @@
+using Content.Server.Clothing.Systems;
+using Content.Server.Preferences.Managers;
+using Content.Shared.Clothing;
+using Content.Shared.Clothing.Components;
+using Content.Shared.Implants;
+using Content.Shared.Implants.Components;
+using Content.Shared.Interaction;
+using Content.Shared.Inventory;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Roles;
+using Content.Shared.Station;
+using Content.Shared.Timing;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Implants;
+
+public sealed class ChameleonControllerSystem : SharedChameleonControllerSystem
+{
+    [Dependency] private readonly IPrototypeManager _proto = default!;
+    [Dependency] private readonly InventorySystem _inventory = default!;
+    [Dependency] private readonly SharedStationSpawningSystem _stationSpawningSystem = default!;
+    [Dependency] private readonly ChameleonClothingSystem _chameleonClothingSystem = default!;
+    [Dependency] private readonly IServerPreferencesManager _preferences = default!;
+    [Dependency] private readonly UseDelaySystem _delay = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<SubdermalImplantComponent, ChameleonControllerSelectedOutfitMessage>(OnSelected);
+
+        SubscribeLocalEvent<ChameleonClothingComponent, InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent>>(ChameleonControllerOutfitItemSelected);
+    }
+
+    private void OnSelected(Entity<SubdermalImplantComponent> ent, ref ChameleonControllerSelectedOutfitMessage args)
+    {
+        if (!_delay.TryResetDelay(ent.Owner, true) || ent.Comp.ImplantedEntity == null || !HasComp<ChameleonControllerImplantComponent>(ent))
+            return;
+
+        ChangeChameleonClothingToOutfit(ent.Comp.ImplantedEntity.Value, args.SelectedChameleonOutfit);
+    }
+
+    /// <summary>
+    ///     Switches all the chameleon clothing that the implant user is wearing to look like the selected job.
+    /// </summary>
+    private void ChangeChameleonClothingToOutfit(EntityUid user, ProtoId<ChameleonOutfitPrototype> outfit)
+    {
+        var outfitPrototype = _proto.Index(outfit);
+
+        _proto.TryIndex(outfitPrototype.Job, out var jobPrototype);
+        _proto.TryIndex(outfitPrototype.StartingGear, out var startingGearPrototype);
+
+        GetJobEquipmentInformation(jobPrototype, user, out var customRoleLoadout, out var defaultRoleLoadout, out var jobStartingGearPrototype);
+
+        var ev = new ChameleonControllerOutfitSelectedEvent(
+            outfitPrototype,
+            customRoleLoadout,
+            defaultRoleLoadout,
+            jobStartingGearPrototype,
+            startingGearPrototype
+            );
+
+        RaiseLocalEvent(user, ref ev);
+    }
+
+    // This gets as much information from the job as it can.
+    // E.g. the players profile, the default equipment for that job etc...
+    private void GetJobEquipmentInformation(
+        JobPrototype? jobPrototype,
+        EntityUid? user,
+        out RoleLoadout? customRoleLoadout,
+        out RoleLoadout? defaultRoleLoadout,
+        out StartingGearPrototype? jobStartingGearPrototype)
+    {
+        customRoleLoadout = null;
+        defaultRoleLoadout = null;
+        jobStartingGearPrototype = null;
+
+        if (jobPrototype == null)
+            return;
+
+        _proto.TryIndex(jobPrototype.StartingGear, out jobStartingGearPrototype);
+
+        if (!TryComp<ActorComponent>(user, out var actorComponent))
+            return;
+
+        var userId = actorComponent.PlayerSession.UserId;
+        var prefs = _preferences.GetPreferences(userId);
+
+        if (prefs.SelectedCharacter is not HumanoidCharacterProfile profile)
+            return;
+
+        var jobProtoId = LoadoutSystem.GetJobPrototype(jobPrototype.ID);
+
+        profile.Loadouts.TryGetValue(jobProtoId, out customRoleLoadout);
+
+        if (!_proto.HasIndex<RoleLoadoutPrototype>(jobProtoId))
+            return;
+
+        defaultRoleLoadout = new RoleLoadout(jobProtoId);
+        defaultRoleLoadout.SetDefault(profile, null, _proto); // only sets the default if the player has no loadout
+    }
+
+    private void ChameleonControllerOutfitItemSelected(Entity<ChameleonClothingComponent> ent, ref InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent> args)
+    {
+        if (!_inventory.TryGetContainingSlot(ent.Owner, out var slot))
+            return;
+
+        _chameleonClothingSystem.SetSelectedPrototype(ent, GetGearForSlot(args, slot.Name), component: ent.Comp);
+    }
+
+    public string? GetGearForSlot(InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent> ev, string slotName)
+    {
+        return GetGearForSlot(ev.Args.ChameleonOutfit, ev.Args.CustomRoleLoadout, ev.Args.DefaultRoleLoadout, ev.Args.JobStartingGearPrototype, ev.Args.StartingGearPrototype, slotName);
+    }
+
+    /// <summary>
+    /// Get the gear for the given slot. The priority is:
+    /// <br/>1.) Custom loadout from the player for the slot.
+    /// <br/>2.) Chameleon outfit slot equipment.
+    /// <br/>3.) Chameleon outfit starting gear equipment.
+    /// <br/>4.) Default job equipment.
+    /// <br/>5.) Staring equipment for that job.
+    /// </summary>
+    /// <returns>The entity (as a protoid) if there is gear for that slot, null if there isn't.</returns>
+    public string? GetGearForSlot(ChameleonOutfitPrototype? chameleonOutfitPrototype, RoleLoadout? customRoleLoadout, RoleLoadout? defaultRoleLoadout, StartingGearPrototype? jobStartingGearPrototype, StartingGearPrototype? startingGearPrototype, string slotName)
+    {
+        var customLoadoutGear = _stationSpawningSystem.GetGearForSlot(customRoleLoadout, slotName);
+        if (customLoadoutGear != null)
+            return customLoadoutGear;
+
+        if (chameleonOutfitPrototype != null && chameleonOutfitPrototype.Equipment.TryGetValue(slotName, out var forSlot))
+            return forSlot;
+
+        var startingGear = startingGearPrototype != null ? ((IEquipmentLoadout)startingGearPrototype).GetGear(slotName) : "";
+        if (startingGear != "")
+            return startingGear;
+
+        var defaultLoadoutGear = _stationSpawningSystem.GetGearForSlot(defaultRoleLoadout, slotName);
+        if (defaultLoadoutGear != null)
+            return defaultLoadoutGear;
+
+        var jobStartingGear = jobStartingGearPrototype != null ? ((IEquipmentLoadout)jobStartingGearPrototype).GetGear(slotName) : "";
+        if (jobStartingGear != "")
+            return jobStartingGear;
+
+        return null;
+    }
+}
index bfa8e1825ddc8bfbec7b959bbed12148d52961fb..282290e46942cf98c39813b460699873812a0d8e 100644 (file)
@@ -11,6 +11,8 @@ using Content.Shared.Access.Components;
 using Content.Shared.CartridgeLoader;
 using Content.Shared.Chat;
 using Content.Shared.DeviceNetwork.Components;
+using Content.Shared.Implants;
+using Content.Shared.Inventory;
 using Content.Shared.Light;
 using Content.Shared.Light.EntitySystems;
 using Content.Shared.PDA;
@@ -56,6 +58,14 @@ namespace Content.Server.PDA
             SubscribeLocalEvent<StationRenamedEvent>(OnStationRenamed);
             SubscribeLocalEvent<EntityRenamedEvent>(OnEntityRenamed, after: new[] { typeof(IdCardSystem) });
             SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertLevelChanged);
+            SubscribeLocalEvent<PdaComponent, InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent>>(ChameleonControllerOutfitItemSelected);
+        }
+
+        private void ChameleonControllerOutfitItemSelected(Entity<PdaComponent> ent, ref InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent> args)
+        {
+            // Relay it to your ID so it can update as well.
+            if (ent.Comp.ContainedId != null)
+                RaiseLocalEvent(ent.Comp.ContainedId.Value, args);
         }
 
         private void OnEntityRenamed(ref EntityRenamedEvent ev)
index a2f59a5a34b2b4d269eac6ce32f83018bb6cac30..dd603cbb0283c6fe8a797106fc849981d5cb4af8 100644 (file)
@@ -204,6 +204,22 @@ public abstract class SharedIdCardSystem : EntitySystem
         return true;
     }
 
+    public bool TryChangeJobDepartment(EntityUid uid, List<ProtoId<DepartmentPrototype>> departments, IdCardComponent? id = null)
+    {
+        if (!Resolve(uid, ref id))
+            return false;
+
+        id.JobDepartments.Clear();
+        foreach (var department in departments)
+        {
+            id.JobDepartments.Add(department);
+        }
+
+        Dirty(uid, id);
+
+        return true;
+    }
+
     /// <summary>
     /// Attempts to change the full name of a card.
     /// Returns true/false.
diff --git a/Content.Shared/Implants/ChameleonControllerImplantComponent.cs b/Content.Shared/Implants/ChameleonControllerImplantComponent.cs
new file mode 100644 (file)
index 0000000..d1874b6
--- /dev/null
@@ -0,0 +1,58 @@
+using Content.Shared.Actions;
+using Content.Shared.Inventory;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Roles;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Implants;
+
+/// <summary>
+///     Will allow anyone implanted with the implant to have more control over their chameleon clothing and items.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ChameleonControllerImplantComponent : Component;
+
+/// <summary>
+///     This is sent when someone clicks on the hud icon and will open the menu.
+/// </summary>
+public sealed partial class ChameleonControllerOpenMenuEvent : InstantActionEvent;
+
+[Serializable, NetSerializable]
+public enum ChameleonControllerKey : byte
+{
+    Key,
+}
+
+[Serializable, NetSerializable]
+public sealed class ChameleonControllerBuiState : BoundUserInterfaceState;
+
+/// <summary>
+///     Triggered when the user clicks on a job in the menu.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class ChameleonControllerSelectedOutfitMessage(ProtoId<ChameleonOutfitPrototype> selectedOutfit) : BoundUserInterfaceMessage
+{
+    public readonly ProtoId<ChameleonOutfitPrototype> SelectedChameleonOutfit = selectedOutfit;
+}
+
+/// <summary>
+///     This event is raised on clothing when the chameleon controller wants it to change sprite based off selecting an
+///      outfit.
+/// </summary>
+/// <param name="ChameleonOutfit">The outfit being switched to.</param>
+/// <param name="CustomRoleLoadout">The users custom loadout for the chameleon outfits job.</param>
+/// <param name="DefaultRoleLoadout">The default loadout for the chameleon outfits job.</param>
+/// <param name="JobStartingGearPrototype">The starting gear of the chameleon outfits job.</param>
+[ByRefEvent]
+public record struct ChameleonControllerOutfitSelectedEvent(
+    ChameleonOutfitPrototype ChameleonOutfit,
+    RoleLoadout? CustomRoleLoadout,
+    RoleLoadout? DefaultRoleLoadout,
+    StartingGearPrototype? JobStartingGearPrototype,
+    StartingGearPrototype? StartingGearPrototype
+) : IInventoryRelayEvent
+{
+    SlotFlags IInventoryRelayEvent.TargetSlots => SlotFlags.WITHOUT_POCKET;
+}
diff --git a/Content.Shared/Implants/ChameleonOutfitPrototype.cs b/Content.Shared/Implants/ChameleonOutfitPrototype.cs
new file mode 100644 (file)
index 0000000..59218a1
--- /dev/null
@@ -0,0 +1,64 @@
+using Content.Shared.Roles;
+using Content.Shared.StatusIcon;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Implants;
+
+/// <summary>
+/// A chameleon clothing outfit. Used for the chameleon controller jobs! Has various fields to help describe a full
+/// job - all the fields are optional and override each other if necessary so you should fill out the maximum amount
+/// that make sense for the best outcome.
+/// </summary>
+[Prototype]
+public sealed partial class ChameleonOutfitPrototype : IPrototype
+{
+    /// <inheritdoc/>
+    [ViewVariables, IdDataField]
+    public string ID { get; private set; } = string.Empty;
+
+    /// <summary>
+    /// Job this outfit is based off of. Will use various things (job icon, job name, loadout etc...) for the outfit.
+    /// This has the lowest priority for clothing if the user has no custom loadout, but highest if they do.
+    /// </summary>
+    [DataField]
+    public ProtoId<JobPrototype>? Job;
+
+    /// <summary>
+    /// Name of the outfit. This will be used for varous things like the chameleon controller UI and the agent IDs job
+    /// name.
+    /// </summary>
+    [DataField]
+    public LocId? Name;
+
+    /// <summary>
+    /// This name is only used in the chameleon controller UI.
+    /// </summary>
+    [DataField]
+    public LocId? LoadoutName;
+
+    /// <summary>
+    /// Generic staring gear. Sometimes outfits don't have jobs but do have starting gear (E.g. Cluwne).
+    /// </summary>
+    [DataField]
+    public ProtoId<StartingGearPrototype>? StartingGear;
+
+    /// <summary>
+    /// Icon for the outfit - used for stuff like the UI or agent ID.
+    /// </summary>
+    [DataField]
+    public ProtoId<JobIconPrototype>? Icon;
+
+    [DataField]
+    public List<ProtoId<DepartmentPrototype>>? Departments;
+
+    [DataField]
+    public bool HasMindShield;
+
+    /// <summary>
+    /// Custom equipment for this specific chameleon outfit. If your making a new outfit that's just for the controller
+    /// use this! It can be mixed with the rest of the fields though, it just takes highest priority right under
+    /// user specified loadouts.
+    /// </summary>
+    [DataField]
+    public Dictionary<string, EntProtoId> Equipment { get; set; } = new();
+}
diff --git a/Content.Shared/Implants/SharedChameleonControllerSystem.cs b/Content.Shared/Implants/SharedChameleonControllerSystem.cs
new file mode 100644 (file)
index 0000000..26df60e
--- /dev/null
@@ -0,0 +1,28 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Implants;
+
+public abstract partial class SharedChameleonControllerSystem : EntitySystem
+{
+    [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ChameleonControllerOpenMenuEvent>(OpenUI);
+    }
+
+    private void OpenUI(ChameleonControllerOpenMenuEvent ev)
+    {
+        var implant = ev.Action.Comp.Container;
+
+        if (!HasComp<ChameleonControllerImplantComponent>(implant))
+            return;
+
+        if (!_uiSystem.HasUi(implant.Value, ChameleonControllerKey.Key))
+            return;
+
+        _uiSystem.OpenUi(implant.Value, ChameleonControllerKey.Key, ev.Performer);
+    }
+}
index f3ab87be389f29d98dabd5878a8aad53819a5455..55adadc4ed421106be64d80125111eb64572e8ca 100644 (file)
@@ -12,6 +12,7 @@ using Content.Shared.Explosion;
 using Content.Shared.Eye.Blinding.Systems;
 using Content.Shared.Gravity;
 using Content.Shared.IdentityManagement.Components;
+using Content.Shared.Implants;
 using Content.Shared.Inventory.Events;
 using Content.Shared.Movement.Events;
 using Content.Shared.Movement.Systems;
@@ -51,6 +52,7 @@ public partial class InventorySystem
         SubscribeLocalEvent<InventoryComponent, ZombificationResistanceQueryEvent>(RelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, IsEquippingTargetAttemptEvent>(RelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, IsUnequippingTargetAttemptEvent>(RelayInventoryEvent);
+        SubscribeLocalEvent<InventoryComponent, ChameleonControllerOutfitSelectedEvent>(RelayInventoryEvent);
 
         // by-ref events
         SubscribeLocalEvent<InventoryComponent, RefreshFrictionModifiersEvent>(RefRelayInventoryEvent);
index 0a7c97420011d3fe89263486e7e664cb66a284aa..c82f2b286393563c61cb5dea5f41da90240ae9b0 100644 (file)
@@ -1,14 +1,27 @@
 using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
+using Content.Shared.Implants;
 using Content.Shared.Mindshield.Components;
+using Content.Shared.Tag;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
 
 namespace Content.Shared.Mindshield.FakeMindShield;
 
 public sealed class SharedFakeMindShieldSystem : EntitySystem
 {
+    [Dependency] private readonly SharedActionsSystem _actions = default!;
+    [Dependency] private readonly TagSystem _tag = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+    // This tag should be placed on the fake mindshield action so there is a way to easily identify it.
+    private static readonly ProtoId<TagPrototype> FakeMindShieldImplantTag = "FakeMindShieldImplant";
+
     public override void Initialize()
     {
         base.Initialize();
         SubscribeLocalEvent<FakeMindShieldComponent, FakeMindShieldToggleEvent>(OnToggleMindshield);
+        SubscribeLocalEvent<FakeMindShieldComponent, ChameleonControllerOutfitSelectedEvent>(OnChameleonControllerOutfitSelected);
     }
 
     private void OnToggleMindshield(EntityUid uid, FakeMindShieldComponent comp, FakeMindShieldToggleEvent toggleEvent)
@@ -16,6 +29,48 @@ public sealed class SharedFakeMindShieldSystem : EntitySystem
         comp.IsEnabled = !comp.IsEnabled;
         Dirty(uid, comp);
     }
+
+    private void OnChameleonControllerOutfitSelected(EntityUid uid, FakeMindShieldComponent component, ChameleonControllerOutfitSelectedEvent args)
+    {
+        if (component.IsEnabled == args.ChameleonOutfit.HasMindShield)
+            return;
+
+        // This assumes there is only one fake mindshield action per entity (This is currently enforced)
+        if (!TryComp<ActionsComponent>(uid, out var actionsComp))
+            return;
+
+        // In case the fake mindshield ever doesn't have an action.
+        var actionFound = false;
+
+        foreach (var action in actionsComp.Actions)
+        {
+            if (!_tag.HasTag(action, FakeMindShieldImplantTag))
+                continue;
+
+            if (!TryComp<ActionComponent>(action, out var actionComp))
+                continue;
+
+            actionFound = true;
+
+            if (_actions.IsCooldownActive(actionComp, _timing.CurTime))
+                continue;
+
+            component.IsEnabled = args.ChameleonOutfit.HasMindShield;
+            Dirty(uid, component);
+
+            if (actionComp.UseDelay != null)
+                _actions.SetCooldown(action, actionComp.UseDelay.Value);
+
+            return;
+        }
+
+        // If they don't have the action for some reason, still set it correctly.
+        if (!actionFound)
+        {
+            component.IsEnabled = args.ChameleonOutfit.HasMindShield;
+            Dirty(uid, component);
+        }
+    }
 }
 
 public sealed partial class FakeMindShieldToggleEvent : InstantActionEvent;
index 8a4733c83401fd05c5903ba8bcc0ef80c6a0e2ad..12d673feda31cc4105a625c5a6fda48305196d85 100644 (file)
@@ -100,6 +100,45 @@ public abstract class SharedJobSystem : EntitySystem
         return false;
     }
 
+    /// <summary>
+    /// Tries to get all the departments for a given job. Will return an empty list if none are found.
+    /// </summary>
+    public bool TryGetAllDepartments(string jobProto, out List<DepartmentPrototype> departmentPrototypes)
+    {
+        // not sorting it since there should only be 1 primary department for a job.
+        // this is enforced by the job tests.
+        var departmentProtos = _prototypes.EnumeratePrototypes<DepartmentPrototype>();
+        departmentPrototypes = new List<DepartmentPrototype>();
+        var found = false;
+
+        foreach (var department in departmentProtos)
+        {
+            if (department.Roles.Contains(jobProto))
+            {
+                departmentPrototypes.Add(department);
+                found = true;
+            }
+        }
+
+        return found;
+    }
+
+    /// <summary>
+    /// Try to get the lowest weighted department for the given job. If the job has no departments will return null.
+    /// </summary>
+    public bool TryGetLowestWeightDepartment(string jobProto, [NotNullWhen(true)] out DepartmentPrototype? departmentPrototype)
+    {
+        departmentPrototype = null;
+
+        if (!TryGetAllDepartments(jobProto, out var departmentPrototypes) || departmentPrototypes.Count == 0)
+            return false;
+
+        departmentPrototypes.Sort((x, y) => y.Weight.CompareTo(x.Weight));
+
+        departmentPrototype = departmentPrototypes[0];
+        return true;
+    }
+
     public bool MindHasJobWithId(EntityUid? mindId, string prototypeId)
     {
 
index dc143667175d15e2173090ea2b108c56fdfeae15..389f696db28dd7d925d831b0b777bf3e8ce37e8c 100644 (file)
@@ -179,4 +179,34 @@ public abstract class SharedStationSpawningSystem : EntitySystem
             RaiseLocalEvent(entity, ref ev);
         }
     }
+
+    /// <summary>
+    ///     Gets all the gear for a given slot when passed a loadout.
+    /// </summary>
+    /// <param name="loadout">The loadout to look through.</param>
+    /// <param name="slot">The slot that you want the clothing for.</param>
+    /// <returns>
+    ///     If there is a value for the given slot, it will return the proto id for that slot.
+    ///     If nothing was found, will return null
+    /// </returns>
+    public string? GetGearForSlot(RoleLoadout? loadout, string slot)
+    {
+        if (loadout == null)
+            return null;
+
+        foreach (var group in loadout.SelectedLoadouts)
+        {
+            foreach (var items in group.Value)
+            {
+                if (!PrototypeManager.TryIndex(items.Prototype, out var loadoutPrototype))
+                    return null;
+
+                var gear = ((IEquipmentLoadout) loadoutPrototype).GetGear(slot);
+                if (gear != string.Empty)
+                    return gear;
+            }
+        }
+
+        return null;
+    }
 }
diff --git a/Resources/Locale/en-US/chameleon-outfits/chameleon-outfits.ftl b/Resources/Locale/en-US/chameleon-outfits/chameleon-outfits.ftl
new file mode 100644 (file)
index 0000000..f6ffe37
--- /dev/null
@@ -0,0 +1 @@
+chameleon-outfit-sus-name = Sus
diff --git a/Resources/Locale/en-US/implant/chameleon-controller.ftl b/Resources/Locale/en-US/implant/chameleon-controller.ftl
new file mode 100644 (file)
index 0000000..d7e3d14
--- /dev/null
@@ -0,0 +1 @@
+chameleon-controller-ui-window-name = Chameleon controls
index c77c1fd5721cee7d302dfd7891b4be7671a247a8..8711b3f118911690701a00245118ce1c2f21591f 100644 (file)
@@ -8,3 +8,5 @@ department-Security = Security
 department-Science = Science
 department-Silicon = Silicon
 department-Specific = Station specific
+
+department-Unknown = Unknown
index 945070dea0a986f198e6228f95b6431b18c1b284..f276c295d99720d601c6f475ee783cf2fb95434d 100644 (file)
     useDelay: 1
   - type: InstantAction
     event: !type:FakeMindShieldToggleEvent
+  - type: Tag
+    tags:
+    - FakeMindShieldImplant
 
 - type: entity
   parent: BaseToggleAction
       state: icon-siren
     useDelay: 1
     itemIconStyle: BigAction
+
+- type: entity
+  id: ActionChameleonController
+  name: Control clothing
+  description: Change your entire outfit fast!
+  components:
+  - type: Action
+    priority: -20
+    icon: { sprite: Actions/Implants/implants.rsi, state: chameleon }
+    itemIconStyle: BigAction
+  - type: InstantAction
+    event: !type:ChameleonControllerOpenMenuEvent
index 7bf804b5e596478d218fee138ef7645116d012df..56bffb73ecdc20473a03460f800d0700a602afcb 100644 (file)
         - id: ClothingEyesChameleon
         - id: ClothingHeadsetChameleon
         - id: ClothingShoesChameleon
-        - id: BarberScissors
+        - id: ChameleonControllerImplanter
 
 - type: entity
   parent: ClothingBackpackDuffelSyndicateBundle
index 3b8ade1dd5d59c782bdd1048ea143abf49d52027..2c4b5fae68185b63f47ff2158a6ceb2242fb35c3 100644 (file)
@@ -44,6 +44,7 @@
   - type: Tag
     tags:
     - HudMedical
+    - WhitelistChameleon
 
 - type: entity
   parent: [ClothingEyesBase, ShowSecurityIcons, BaseSecurityContraband]
@@ -58,6 +59,7 @@
   - type: Tag
     tags:
     - HudSecurity
+    - WhitelistChameleon
 
 - type: entity
   parent: [ClothingEyesBase, BaseCommandContraband]
index eb370279995fe544bafabb9626970f6dd771afa7..d0fe3551dfbf221ab12af41b36407766680c95cd 100644 (file)
@@ -29,6 +29,7 @@
     - Bandana
     - ClothMade
     - Recyclable
+    - WhitelistChameleon
 
 - type: entity
   parent: [ClothingHeadBandBase, ClothingMaskBandBlack]
index 031fcb998818223251ae7730fc2ae05c057b1811..677727d65df9bc2e3491a4b0c8d40924565cf596 100644 (file)
@@ -12,6 +12,7 @@
   - type: Tag
     tags:
       - Medal
+      - WhitelistChameleon
 
 - type: entity
   parent: ClothingNeckBase
@@ -28,6 +29,7 @@
   - type: Tag
     tags:
       - Medal
+      - WhitelistChameleon
 
 - type: entity
   parent: ClothingNeckBase
@@ -42,6 +44,7 @@
   - type: Tag
     tags:
       - Medal
+      - WhitelistChameleon
 
 - type: entity
   parent: ClothingNeckBase
@@ -56,6 +59,7 @@
   - type: Tag
     tags:
       - Medal
+      - WhitelistChameleon
 
 - type: entity
   parent: ClothingNeckBase
@@ -70,6 +74,7 @@
   - type: Tag
     tags:
       - Medal
+      - WhitelistChameleon
 
 - type: entity
   parent: ClothingNeckBase
@@ -84,6 +89,7 @@
   - type: Tag
     tags:
       - Medal
+      - WhitelistChameleon
 
 - type: entity
   parent: ClothingNeckBase
   - type: Tag
     tags:
       - Medal
+      - WhitelistChameleon
 
 - type: entity
   parent: ClothingNeckBase
   - type: Tag
     tags:
       - Medal
+      - WhitelistChameleon
index 2c73342dbecd3c854bd7a2dc20eb1c1e488ef779..9668893b5f7ec2583df9f278f841f018e7545d46 100644 (file)
   - type: Tag
     tags:
     - MonkeyWearable
+    - WhitelistChameleon
 
 #Wizard Hardsuit
 - type: entity
index a4d3403db40ccbf025a83c9b1628b49aecf7550c..fd466e61fa017e24665101ff9e03b590b4808fa7 100644 (file)
@@ -31,6 +31,7 @@
   - type: Tag
     tags:
     - HiViz
+    - WhitelistChameleon
 
 #(Bartender) vest
 - type: entity
index f853069964e4cf71e6c0b3cfabf87ba36da2e6eb..9e89f1c8a75c4ae8ae6543fb0a2a3028985bef53 100644 (file)
     - type: Implanter
       implant: DnaScramblerImplant
 
+- type: entity
+  id: ChameleonControllerImplanter
+  suffix: chameleon controller
+  parent: BaseImplantOnlyImplanterSyndi
+  components:
+  - type: Implanter
+    implant: ChameleonControllerImplant
+
 #Nuclear Operative/Special implanters
 
 - type: entity
index 13b906bbd7f5f931c3e2ca6c446d8e5d8daca3c9..7ddf4945d06a355230e26360c8ea52d6ef8f6656 100644 (file)
         components:
         - HumanoidAppearance # syndies cant turn hamlet into a human
 
+- type: entity
+  categories: [ HideSpawnMenu, Spawner ]
+  parent: BaseSubdermalImplant
+  id: ChameleonControllerImplant
+  name: chameleon controller implant
+  description: This implant allows you to instantly change the appearance of all worn chameleon clothing.
+  components:
+  - type: ChameleonControllerImplant
+  - type: SubdermalImplant
+    implantAction: ActionChameleonController
+  - type: UseDelay
+    delay: 1
+  - type: UserInterface
+    interfaces:
+      enum.ChameleonControllerKey.Key:
+        type: ChameleonControllerBoundUserInterface
+
 #Nuclear Operative/Special Exclusive implants
 
 - type: entity
diff --git a/Resources/Prototypes/Loadouts/loadouts_chameleon.yml b/Resources/Prototypes/Loadouts/loadouts_chameleon.yml
new file mode 100644 (file)
index 0000000..e69de29
index 6a9a65bb13ec8784621b47bf7551c78c4810f9ce..9cacfab581b7dbb038e58e00eae47109420bf310 100644 (file)
     - Wirecutter
     - Welder
     - Multitool
+
+- type: chameleonOutfit
+  id: NinjaChameleonOutfit
+  name: roles-antag-space-ninja-name
+  startingGear: SpaceNinjaGear
+  equipment:
+    id: PassengerPDA
+    neck: ClothingNeckGoldmedal
index 8f1094802c78dc8adfd5ad5828ace030c0d772a9..4097e5573c3c38253b336a242fdfa6ce7582e801 100644 (file)
   equipment:
     pocket2: BaseUplinkRadio40TC
 
+- type: chameleonOutfit
+  id: NukeopsOutfit
+  name: roles-antag-nuclear-operative-name
+  startingGear: SyndicateOperativeGearFullNoUplink
+  icon: "JobIconSyndicate"
+  equipment:
+    head: ClothingHeadHelmetHardsuitSyndie
+    neck: ClothingNeckScarfStripedSyndieRed
+
 #Nuclear Operative Commander Gear
 - type: startingGear
   id: SyndicateCommanderGearFull
   inhand:
   - NukeOpsDeclarationOfWar
 
+- type: chameleonOutfit
+  id: NukeopsCommanderOutfit
+  name: roles-antag-nuclear-operative-commander-name
+  startingGear: SyndicateCommanderGearFull
+  icon: "JobIconSyndicate"
+  equipment:
+    head: ClothingHeadHelmetHardsuitSyndieCommander
+    neck: ClothingNeckScarfStripedSyndieGreen
+
 #Nuclear Operative Medic Gear
 - type: startingGear
   id: SyndicateOperativeMedicFull
     - CombatMedipen
     - DeathAcidifierImplanter
 
+- type: chameleonOutfit
+  id: NukeopsMedicOutfit
+  name: roles-antag-nuclear-operative-agent-name
+  startingGear: SyndicateOperativeMedicFull
+  icon: "JobIconSyndicate"
+  equipment:
+    head: ClothingHeadHelmetHardsuitSyndieMedic
+    neck: ClothingNeckScarfStripedLightBlue
+
 #Lone Operative Gear
 - type: startingGear
   id: SyndicateLoneOperativeGearFull
index cd71ab05b5648644bee086bdd2bdfc399997f5bc..ae64cb438af851174b108d34d3aa0d5232fe6bc2 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: CargoTechnicianChameleonOutfit
+  job: CargoTechnician
+  equipment:
+    head: ClothingHeadHatCargosoft
+    eyes: ClothingEyesGlassesCheapSunglasses
+    mask: ClothingMaskBreath
+    outerClothing: ClothingOuterWinterCargo
+    neck: ClothingNeckScarfStripedBrown
+    gloves: ClothingHandsGlovesFingerless
index e0cbc95690893e5554fcd8d14133ff0dfbeb7bff..f2e7a2fb93af990cd9593757a48ea897f225512a 100644 (file)
   storage:
     back:
     - Flash
+
+- type: chameleonOutfit
+  id: QuartermasterChameleonOutfit
+  job: Quartermaster
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHatQMsoft
+    eyes: ClothingEyesGlassesSunglasses
+    mask: ClothingMaskBreath
+    outerClothing: ClothingOuterWinterQM
+    neck: ClothingNeckCloakQm
+    gloves: ClothingHandsKnuckleDustersQM
index 8a1811f0c931a9f8e0aacda41867a14b3c2e708a..9bab10529b673d0628266d468f6dbe833e52f7f4 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: SalvageSpecialistChameleonOutfit
+  job: SalvageSpecialist
+  equipment:
+    head: ClothingHeadHatCargosoft
+    eyes: ClothingEyesGlassesCheapSunglasses
+    mask: ClothingMaskGasExplorer
+    outerClothing: ClothingOuterWinterMiner
+    neck: ClothingNeckScarfStripedBrown
+    gloves: ClothingHandsGlovesColorBlack
+
index aeabd38e41e5d99e05379af59068d227bc77e266..2ba54d48b1f7a2d9a3aeabe8266b54e87490456e 100644 (file)
     - GrenadeFlashBang
     - PillAmbuzolPlus
     - PillAmbuzol
+
+- type: chameleonOutfit
+  id: CBURNChameleonOutfit
+  job: CBURN
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHelmetCBURN
+    neck: ClothingNeckScarfStripedBrown
index e46c114ef47eafe8a740f412faca8896d2d5bc47..3231783fd3d564e8930975ddb7cb29d52a2bc216 100644 (file)
     - FreedomImplanter
   inhand:
   - WeaponPulseRifle
+
+- type: chameleonOutfit
+  id: DeathSquadChameleonOutfit
+  job: DeathSquad
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHelmetHardsuitDeathsquad
+    neck: ClothingNeckBronzeheart
index 2e47f1f864a47674105934c840afda034bdeec19..1423db752aa059dc46ac896c3a5ec4a490764b13 100644 (file)
     - CrowbarRed
     - MagazineMagnum
 
+- type: chameleonOutfit
+  id: ERTLeaderChameleonOutfit
+  job: ERTLeader
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHelmetHardsuitERTLeader
+    neck: ClothingNeckBronzeheart
+
 # Chaplain
 - type: job
   id: ERTChaplain
     - FoodBakedBunHotX
     - Lighter
 
+- type: chameleonOutfit
+  id: ERTChaplainChameleonOutfit
+  job: ERTChaplain
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHelmetHardsuitERTChaplain
+    mask: ClothingMaskGasERT
+    outerClothing: ClothingOuterHardsuitERTChaplain
+    shoes: ClothingShoesBootsMagAdv
+
 # Engineer
 - type: job
   id: ERTEngineer
     - SheetSteel
     - SheetGlass
 
+- type: chameleonOutfit
+  id: ERTEngineerChameleonOutfit
+  job: ERTEngineer
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHelmetHardsuitERTEngineer
+    neck: ClothingNeckEngineermedal
+
 # Security
 - type: job
   id: ERTSecurity
   description: job-description-ertsecurity
   playTimeTracker: JobERTSecurity
   setPreference: false
-  startingGear: ERTEngineerGearEVA
+  startingGear: ERTSecurityGearEVA
   icon: "JobIconNanotrasen"
   supervisors: job-supervisors-centcom
   canBeAntag: false
     - CrowbarRed
     - MagazinePistol
 
+- type: chameleonOutfit
+  id: ERTSecurityChameleonOutfit
+  job: ERTSecurity
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHelmetHardsuitERTSecurity
+    neck: ClothingNeckSecuritymedal
+
 # Medical
 - type: job
   id: ERTMedical
     - ChemistryBottleEpinephrine
     - ChemistryBottleEpinephrine
 
+- type: chameleonOutfit
+  id: ERTMedicalChameleonOutfit
+  job: ERTMedical
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHelmetHardsuitERTMedical
+    neck: ClothingNeckStethoscope
+
 # Janitor
 - type: job
   id: ERTJanitor
     - Soap
     - CrowbarRed
     - AdvMopItem
+
+- type: chameleonOutfit
+  id: ERTJanitorChameleonOutfit
+  job: ERTJanitor
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHelmetHardsuitERTJanitor
+    neck: ClothingNeckScarfStripedPurple
+    eyes: ClothingEyesGlassesSunglasses
index 10fb00448ec9435552a0c880924fccfb724c9ea7..68fc11b679d41f27cdf601c8149f5e469049faa4 100644 (file)
     belt: WeaponPistolN1984
     pocket1: BoxFolderBlack
     pocket2: PenCentcom
+
+
+- type: chameleonOutfit
+  id: CentralCommandOfficialOutfit
+  job: CentralCommandOfficial
+  hasMindShield: true
+  equipment:
+    neck: ClothingNeckScarfStripedCentcom
+    mask: ClothingMaskGasCentcom
index 5155c8ca00e59ffb5c1f0291e6c8e5ba486e9594..c1da684db174b056129f176e792ef8eca68a939c 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: PassengerChameleonOutfit
+  job: Passenger
+  equipment:
+    head: ClothingHeadHatWelding
+    eyes: ClothingEyesGlassesCheapSunglasses
+    mask: ClothingMaskGas
+    neck: ClothingNeckMantle
+    outerClothing: ClothingOuterWinterCoat
+    gloves: ClothingHandsGlovesColorYellowBudget
index 3306c4119686d8cfbd54b4a16d1e65abc60827b8..8881fb0d412718c06b0ce13c5decd3909842bd4e 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: BartenderChameleonOutfit
+  job: Bartender
+  equipment:
+    head: ClothingHeadHatTophat
+    eyes: ClothingEyesGlassesSunglasses
+    mask: ClothingMaskBreath
+    neck: ClothingNeckScarfStripedBlack
+    outerClothing: ClothingOuterArmorBasicSlim
+    gloves: ClothingHandsGlovesColorBlack
+
index 47c1ccca0228e73c411632403218be97c04684f3..f5f05bded9fc788e0c445cf993f0fabc71f4dc3c 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: BotanistChameleonOutfit
+  job: Botanist
+  equipment:
+    head: ClothingHeadBandBotany
+    eyes: ClothingEyesGlassesCheapSunglasses
+    mask: ClothingMaskBreath
+    neck: ClothingNeckScarfStripedGreen
+    outerClothing: ClothingOuterApronBotanist
+    gloves: ClothingHandsGlovesLeather
index 1ab77dbae58d7e1419f0988cbe96035419d0407d..e384801cc5a992dafc30d5a797b0a24bd1819030 100644 (file)
     back:
     - Bible
     - RubberStampChaplain
+
+- type: chameleonOutfit
+  id: ChaplainChameleonOutfit
+  job: Chaplain
+  equipment:
+    head: ClothingHeadHatPlaguedoctor
+    eyes: ClothingEyesGlasses
+    mask: ClothingMaskPlague
+    neck: ClothingNeckStoleChaplain
+    outerClothing: ClothingOuterPlagueSuit
+    gloves: ClothingHandsGlovesColorBlack
+
index 8ea6dd1acf0f6592ab6380d5c45c1d042cfce9fd..fd3941d4e0382be61ba6854b8d4eea8b3df63afb 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: ChefChameleonOutfit
+  job: Chef
+  equipment:
+    head: ClothingHeadHatChef
+    eyes: ClothingEyesGlassesCheapSunglasses
+    mask: ClothingMaskItalianMoustache
+    neck: ClothingNeckScarfStripedBrown
+    outerClothing: ClothingOuterJacketChef
+    gloves: ClothingHandsGlovesColorWhite
+    shoes: ClothingShoesChef
index b8f289e70eb18330bcde075bafc982de6ac93fc6..60821cfe76fe85795baef0ba4f8fe1e437f30a4d 100644 (file)
     back:
     - RubberStampClown
     - CrayonRainbow
+
+- type: chameleonOutfit
+  id: ClownChameleonOutfit
+  job: Clown
+  equipment:
+    head: ClothingHeadHatXmasCrown
+    eyes: ClothingEyesGlassesCheapSunglasses
+    neck: ClothingHeadHatFlowerWreath
+    outerClothing: ClothingOuterClownPriest
+    gloves: ClothingHandsGlovesColorYellowBudget
index d361348a6e0f1a2f567569db160e010fae5c1fff..15a19b65555719fcc0bd0a82184501f0effe4a7c 100644 (file)
     head: ClothingHeadHatCatEars
     ears: ClothingHeadsetService
     belt: ClothingBeltJanitorFilled
+
+- type: chameleonOutfit
+  id: JanitorChameleonOutfit
+  job: Janitor
+  equipment:
+    head: ClothingHeadHatPurplesoft
+    eyes: ClothingEyesGlassesCheapSunglasses
+    mask: ClothingMaskGas
+    neck: ClothingNeckScarfStripedPurple
+    outerClothing: ClothingOuterWinterJani
+    gloves: ClothingHandsGlovesJanitor
index 3cd8258e59948016bb14c78f0a87369c24cb7545..81c8b71050a991e23ab89d4c72a4516e787c753f 100644 (file)
     back:
     - RubberStampLawyer
     - BookSpaceLaw
+
+- type: chameleonOutfit
+  id: LawyerChameleonOutfit
+  job: Lawyer
+  equipment:
+    head: ClothingHeadHatBowlerHat
+    eyes: ClothingEyesGlassesCheapSunglasses
+    mask: ClothingMaskBreath
+    neck: ClothingNeckLawyerbadge
+    outerClothing: ClothingOuterWinterColorBlack
+    gloves: ClothingHandsGlovesColorBlack
index 61f0ea1d321e4b13d0cc572d9e4a2688e55c53b2..080447979f4b5abd63b35791319ead43321a9229 100644 (file)
   storage:
     back:
     - BookRandom
+
+- type: chameleonOutfit
+  id: LibrarianChameleonOutfit
+  job: Librarian
+  equipment:
+    head: ClothingHeadHatCanadaBeanie
+    eyes: ClothingEyesGlassesJamjar
+    mask: ClothingMaskGas
+    neck: ClothingNeckScarfStripedGreen
+    outerClothing: ClothingOuterWinterColorGreen
+    gloves: ClothingHandsGlovesColorBlack
index 3fb89a719ceac496b988e329b2ba89c60c28cb22..293df524e9412e97494ae82a6d265ef3e7fe25dc 100644 (file)
       state: full
   - type: InstantAction
     event: !type:InvisibleWallActionEvent
+
+- type: chameleonOutfit
+  id: MimeChameleonOutfit
+  job: Mime
+  equipment:
+    head: ClothingHeadHatMimesoft
+    eyes: ClothingEyesGlassesCheapSunglasses
+    neck: ClothingNeckScarfStripedZebra
+    outerClothing: ClothingOuterWinterMime
index b33525d2054ef5df14740b8e9f37aaed9621f865..04891ab2c695c6760a07b4539ae05ba999fc9277 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: MusicianChameleonOutfit
+  job: Musician
+  equipment:
+    head: ClothingHeadHatTophat
+    mask: ClothingMaskBreath
+    neck: ClothingNeckHeadphones
+    outerClothing: ClothingOuterWinterMusician
+    gloves: ClothingHandsGlovesColorBlack
index 9dd79cfabe06fb66299444455b44f1617dcf6863..f98dac80ed1bcfc05622c03414b1a0119b0a332f 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: ServiceWorkerChameleonOutfit
+  job: ServiceWorker
+  equipment:
+    head: ClothingHeadHatBowlerHat
+    eyes: ClothingEyesGlassesSunglasses
+    mask: ClothingMaskGas
+    neck: ClothingNeckScarfStripedBrown
+    outerClothing: ClothingOuterVest
+    gloves: ClothingHandsGlovesColorBlack
index 33fc261922235ba4b94f189ab51a6796458b7cff..bc7aef6ab08cdf5d75b246f724e9b2b6e6ecc855 100644 (file)
     back:
     - Flash
     # - StationCharter
+
+- type: chameleonOutfit
+  id: CaptainChameleonOutfit
+  job: Captain
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHatCapcap
+    eyes: ClothingEyesGlassesCommand
+    mask: ClothingMaskGasCaptain
+    neck: ClothingNeckCloakCap
index 5f4691b250b195173e4f8565fe63be8678a4e7b5..5bdbe47e80e04503ff3978c79440e4d5456de15a 100644 (file)
   storage:
     back:
     - Flash
+
+- type: chameleonOutfit
+  id: HeadOfPersonnelChameleonOutfit
+  job: HeadOfPersonnel
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHatHopcap
+    eyes: ClothingEyesHudCommand
+    mask: ClothingMaskNeckGaiterRed
+    neck: ClothingNeckCloakHop
+    outerClothing: ClothingOuterWinterHoP
+
index 007b1d11745099760e4847b6779bf4dc81d1b712..5dda67f25d8437fae4a5020e5887789507eacf37 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: AtmosphericTechnicianChameleonOutfit
+  job: AtmosphericTechnician
+  equipment:
+    head: ClothingHeadHelmetAtmosFire
+    mask: ClothingMaskGasAtmos
+    neck: ClothingNeckScarfStripedLightBlue
+    outerClothing: ClothingOuterSuitAtmosFire
+    gloves: ClothingHandsGlovesColorYellow
index 5550e02840709386135db54ee67f0b5407510665..264e31a3aedb9634e906665b02f3924093c6d332 100644 (file)
   storage:
     back:
     - Flash
+
+- type: chameleonOutfit
+  id: ChiefEngineerChameleonOutfit
+  job: ChiefEngineer
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHatBeretEngineering
+    mask: ClothingMaskBreath
+    neck: ClothingNeckCloakCe
+    outerClothing: ClothingOuterWinterCE
+    gloves: ClothingHandsGlovesColorYellow
+    shoes: ClothingShoesBootsMagAdv
index 17a6e67ae2c3587bb8c60737196398d318ad724e..c67e3067cb5758e64292462089deaeba5aa95954 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: StationEngineerChameleonOutfit
+  job: StationEngineer
+  equipment:
+    head: ClothingHeadHatHardhatYellow
+    mask: ClothingMaskBreath
+    neck: ClothingNeckScarfStripedOrange
+    outerClothing: ClothingOuterWinterEngi
+    gloves: ClothingHandsGlovesColorYellow
+    shoes: ClothingShoesBootsMag
index 23ad4ca401f4d0dfbc2f45769147b435f6eebe4b..24cef50e1a7f7f193f5450b159c1c9586f607daf 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: TechnicalAssistantChameleonOutfit
+  job: TechnicalAssistant
+  equipment:
+    head: ClothingHeadHatHardhatOrange
+    eyes: ClothingEyesGlassesMeson
+    mask: ClothingMaskBreath
+    neck: ClothingNeckScarfStripedOrange
+    outerClothing: ClothingOuterVestHazard
+    gloves: ClothingHandsGlovesColorYellow
index a797208c323d7234e441bfa548b25d56598196bf..7ec68a5d5247f9b88631ad9f87560f83a2a9dbb9 100644 (file)
   equipment:
     pocket1: BikeHorn
 
+- type: chameleonOutfit
+  id: CluwneChameleonOutfit
+  name: job-title-cluwne
+  startingGear: CluwneGear
+  icon: JobIconCluwne
+  equipment:
+    head: ClothingHeadHatXmasCrown
+    eyes: ClothingEyesBlindfold
+    ears: ClothingHeadsetGrey
+    neck: ClothingHeadHatFlowerWreath
+    outerClothing: ClothingOuterClownPriest
diff --git a/Resources/Prototypes/Roles/Jobs/Fun/sus.yml b/Resources/Prototypes/Roles/Jobs/Fun/sus.yml
new file mode 100644 (file)
index 0000000..818feb8
--- /dev/null
@@ -0,0 +1,17 @@
+- type: chameleonOutfit
+  id: SusChameleonOutfit
+  loadoutName: chameleon-outfit-sus-name
+  name: job-name-passenger
+  icon: "JobIconPassenger"
+  equipment:
+    head: ClothingHeadHatBeret
+    eyes: ClothingEyesGlassesSunglasses
+    ears: ClothingHeadsetGrey
+    mask: ClothingMaskGas
+    outerClothing: ClothingOuterVest
+    jumpsuit: ClothingUniformJumpsuitColorBlack
+    neck: ClothingNeckScarfStripedRed
+    back: ClothingBackpack
+    gloves: ClothingHandsGlovesColorBlack
+    shoes: ClothingShoesColorBlack
+    id: PassengerPDA
index d137df195ee79572d31f4b8aa02a19082b594346..e59e08bf79d084418cef3c952d7a0b9da0f83f19 100644 (file)
     jumpsuit: ClothingUniformJumpsuitColorPurple
     head: ClothingHeadHatVioletwizard
     outerClothing: ClothingOuterWizardViolet
+
+- type: chameleonOutfit
+  id: WizardChameleonOutfit
+  name: roles-antag-wizard-name
+  startingGear: WizardBlueGear
+  equipment:
+    eyes: ClothingEyesGlassesCheapSunglasses
+    mask: ClothingMaskBreath
+    neck: ClothingNeckLGBTPin
+    gloves: ClothingHandsGlovesColorBlack
index 830af18b9b56c70450213a052465896111155c97..e14fbd5b84dba48142f1c6b112d7712ad8a5b1ae 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: ChemistChameleonOutfit
+  job: Chemist
+  equipment:
+    head: ClothingHeadHatBeretMedic
+    mask: ClothingMaskSterile
+    neck: ClothingNeckStethoscope
+    outerClothing: ClothingOuterCoatLabChem
+    gloves: ClothingHandsGlovesLatex
index 679c3c883ad0c39097a67e4b04d02cd39cf12cfd..b6698091df734a821b366c0bb33f4f814b4e8e84 100644 (file)
   storage:
     back:
     - Flash
+
+- type: chameleonOutfit
+  id: ChiefMedicalOfficerChameleonOutfit
+  job: ChiefMedicalOfficer
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHatBeretCmo
+    eyes: ClothingEyesHudMedical
+    mask: ClothingMaskSterile
+    neck: ClothingCloakCmo
+    outerClothing: ClothingOuterCoatLabCmo
+    gloves: ClothingHandsGlovesNitrile
index f0f39d156fa8a9fcb53af412262ba946200727f1..4b43df2004fa1693e9925b604aaf74ebeaff81a8 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: MedicalDoctorChameleonOutfit
+  job: MedicalDoctor
+  equipment:
+    head: ClothingHeadNurseHat
+    eyes: ClothingEyesHudMedical
+    mask: ClothingMaskSterile
+    neck: ClothingNeckStethoscope
+    outerClothing: ClothingOuterCoatLab
+    gloves: ClothingHandsGlovesLatex
index f7414d75aba5cc4472e6687b3af5fdd2280076bd..d313d12d9b7a038e8f22e9d712e622e6bb39d3cb 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: MedicalInternChameleonOutfit
+  job: MedicalIntern
+  equipment:
+    head: ClothingHeadHatBeretMedic
+    eyes: ClothingEyesHudMedical
+    mask: ClothingMaskSterile
+    neck: ClothingNeckStethoscope
+    outerClothing: ClothingOuterCoatLabOpened
+    gloves: ClothingHandsGlovesLatex
index 22b985e8d7b7b38e00a07cceb36dbb5688442a17..5526e63f249dbeb0df65cfcd49c829a8b769b701 100644 (file)
   storage:
     back:
     - EmergencyRollerBedSpawnFolded
+
+- type: chameleonOutfit
+  id: ParamedicChameleonOutfit
+  job: Paramedic
+  equipment:
+    head: ClothingHeadHatParamedicsoft
+    eyes: ClothingEyesHudMedical
+    mask: ClothingMaskSterile
+    neck: ClothingNeckStethoscope
+    outerClothing: ClothingOuterCoatParamedicWB
+    gloves: ClothingHandsGlovesLatex
index 8dba235d8929eff1983757f291186c498053b9a6..e824368cb77a938f5ac0ad7e6bd04274d9f4ab0f 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: ResearchAssistantChameleonOutfit
+  job: ResearchAssistant
+  equipment:
+    head: ClothingHeadHatCardborg
+    eyes: ClothingEyesGlasses
+    mask: ClothingMaskGas
+    neck: ClothingNeckScarfStripedPurple
+    outerClothing: ClothingOuterCoatLab
+    gloves: ClothingHandsGlovesLatex
index bc341907d77f8ee44955def1f1442bd2b347c9fc..da41d9f3e045d043142b6e4a0f6df481873d5824 100644 (file)
   storage:
     back:
     - Flash
+
+- type: chameleonOutfit
+  id: ResearchDirectorChameleonOutfit
+  job: ResearchDirector
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHatBeretRND
+    eyes: ClothingEyesGlasses
+    mask: ClothingMaskGas
+    neck: ClothingNeckCloakRd
+    outerClothing: ClothingOuterCoatRD
+    gloves: ClothingHandsGlovesColorPurple
index cefd26c450579da2b7948ab2f52a4bb066955b10..e2e31f8dcfe13e3358ac750ba0ab21ae13f68298 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: ScientistChameleonOutfit
+  job: Scientist
+  equipment:
+    head: ClothingHeadBandSkull
+    eyes: ClothingEyesGlassesCheapSunglasses
+    mask: ClothingMaskGas
+    neck: ClothingNeckTieSci
+    outerClothing: ClothingOuterCoatRnd
+    gloves: ClothingHandsGlovesLatex
index 19f14eda95a455ec361b746c5eac20c6bb8ef1cb..e5c9fdf2dc083840c452d91911a20bdf11038205 100644 (file)
     - Flash
     - ForensicPad
     - ForensicScanner
+
+- type: chameleonOutfit
+  id: DetectiveChameleonOutfit
+  job: Detective
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHatFedoraBrown
+    mask: ClothingMaskGasSecurity
+    neck: ClothingNeckTieDet
+    gloves: ClothingHandsGlovesForensic
index edf45ca944b584b045870e039b30790f4b6ae9aa..7db7429263a61e3418be29018feeb3bc3da5c4d5 100644 (file)
     back:
     - Flash
     - MagazinePistol
+
+- type: chameleonOutfit
+  id: HeadOfSecurityChameleonOutfit
+  job: HeadOfSecurity
+  hasMindShield: true
+  equipment:
+    mask: ClothingMaskGasSecurity
+    neck: ClothingNeckCloakHos
index 260f92b58702761693ca4c1d487b21ffb4bb9b15..cac79f48227119ac731299ea0ff461d98191a59c 100644 (file)
     back:
     - Flash
     - MagazinePistol
+
+- type: chameleonOutfit
+  id: SecurityCadetChameleonOutfit
+  job: SecurityCadet
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHelmetBasic
+    eyes: ClothingEyesHudSecurity
+    mask: ClothingMaskGasSecurity
+    neck: ClothingNeckScarfStripedRed
+    belt: ClothingBeltSecurity
+    gloves: ClothingHandsGlovesColorBlack
index af074c17aa17670aa60a6fee3a8324d9b2e0334f..432c6b0d3cd03ce0e71393f24380319995fd5b9a 100644 (file)
     back:
     - Flash
     - MagazinePistol
+
+- type: chameleonOutfit
+  id: SecurityOfficerChameleonOutfit
+  job: SecurityOfficer
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHatBeretSecurity
+    mask: ClothingMaskGasSecurity
+    neck: Dinkystar
+    gloves: ClothingHandsGlovesColorBlack
index 0a49023a441294de8883e9ff32fa25ce6b089486..42964ee134f820b4e8a14c8fb11d8f403cb59bdd 100644 (file)
     back:
     - Flash
     - MagazinePistol
+
+- type: chameleonOutfit
+  id: WardenChameleonOutfit
+  job: Warden
+  hasMindShield: true
+  equipment:
+    head: ClothingHeadHatWarden
+    mask: ClothingMaskGasSecurity
+    outerClothing: ClothingOuterCoatWarden
+    jumpsuit: ClothingUniformJumpsuitWarden
+    neck: Dinkystar
+    belt: ClothingBeltSecurity
+    gloves: ClothingHandsGlovesCombat
+    shoes: ClothingShoesBootsJack
index 8eb28c273e5d786034ae1c4a15f209c951ffc1ec..bcf7d065c463cee3923712b6f7bd450fe35e7533 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: BoxerChameleonOutfit
+  job: Boxer
+  equipment:
+    head: ClothingHeadHatBlacksoft
+    eyes: ClothingEyesGlassesCheapSunglasses
+    mask: ClothingMaskBreath
+    outerClothing: ClothingOuterWinterColorGray
+    jumpsuit: UniformShortsRedWithTop
+    neck: ClothingNeckScarfStripedRed
index 74e45a605b287ead82d2939f439d19bdc3d061f9..e43bab3e72c80145600b010192a1855b2de2c73a 100644 (file)
   storage:
     back:
     - RubberStampPsychologist
+
+- type: chameleonOutfit
+  id: PsychologistChameleonOutfit
+  job: Psychologist
+  equipment:
+    head: ClothingHeadHatBeretMedic
+    eyes: ClothingEyesHudMedical
+    mask: ClothingMaskSterile
+    outerClothing: ClothingOuterCoatLab
+    neck: ClothingNeckStethoscope
+    belt: ClothingBeltMedical
+    gloves: ClothingHandsGlovesLatex
index d0a35990d708efa4646b96574c7d1e4eae62bc24..c3c61acd967b01747d856bfed4eb4ade62b96e8f 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: ReporterChameleonOutfit
+  job: Reporter
+  equipment:
+    head: ClothingHeadHatFedoraGrey
+    eyes: ClothingEyesGlassesCheapSunglasses
+    mask: ClothingMaskBreath
+    outerClothing: ClothingOuterCoatTrench
+    neck: ClothingNeckTieRed
+    gloves: ClothingHandsGlovesFingerless
index 879a5a3af4fbff5a3acaa2d93fa9d3e49d803e08..643e372a2f01cf665c483ba489b0526402ab435e 100644 (file)
   #storage:
     #back:
     #- Stuff
+
+- type: chameleonOutfit
+  id: ZookeeperChameleonOutfit
+  job: Zookeeper
+  equipment:
+    eyes: ClothingEyesGlassesCheapSunglasses
+    mask: ClothingMaskBreath
+    outerClothing: ClothingOuterWinterColorLightBrown
+    neck: ClothingNeckScarfStripedBrown
+    belt: ClothingBeltStorageWaistbag
+    gloves: ClothingHandsGlovesColorBrown
+
index 47feb6824815077a89cd6af53b4ce395e22e7232..69a4544486aeb7de0a4df16f29155f8b9dce34f5 100644 (file)
 - type: Tag
   id: MimeHappyHonk
 
+- type: Tag
+  id: FakeMindShieldImplant
+
 - type: Tag
   id: MindTransferTarget
 
diff --git a/Resources/Textures/Actions/Implants/implants.rsi/chameleon.png b/Resources/Textures/Actions/Implants/implants.rsi/chameleon.png
new file mode 100644 (file)
index 0000000..86734f6
Binary files /dev/null and b/Resources/Textures/Actions/Implants/implants.rsi/chameleon.png differ
index a3e37f3298c1961b0ad4479ae533aec7120bcef5..97b19c98561c4aaf24305f17e14f1e50adf93420 100644 (file)
@@ -1,7 +1,7 @@
 {
   "version": 1,
   "license": "CC-BY-SA-3.0",
-  "copyright": "Implant icons taken from Citadel Station at commit https://github.com/Citadel-Station-13/Citadel-Station-13/commit/a2f6a7c20763da3d2f3cfb982e9ccd7922df6162",
+  "copyright": "Implant icons taken from Citadel Station at commit https://github.com/Citadel-Station-13/Citadel-Station-13/commit/a2f6a7c20763da3d2f3cfb982e9ccd7922df6162. The chameleon icon is a combination of the red beret (https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e) and the CMO beret (https://github.com/space-wizards/space-station-14/pull/13489) made by Beck",
   "size": {
     "x": 32,
     "y": 32
@@ -12,6 +12,9 @@
     },
     {
       "name": "explosive"
+    },
+    {
+      "name": "chameleon"
     }
   ]
 }