--- /dev/null
+using Content.Shared.Implants;
+
+namespace Content.Client.Implants;
+
+public sealed partial class ChameleonControllerSystem : SharedChameleonControllerSystem;
--- /dev/null
+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);
+ }
+}
--- /dev/null
+<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>
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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));
+ }
+
+}
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
{
[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()
{
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)
--- /dev/null
+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;
+ }
+}
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;
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)
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.
--- /dev/null
+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;
+}
--- /dev/null
+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();
+}
--- /dev/null
+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);
+ }
+}
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;
SubscribeLocalEvent<InventoryComponent, ZombificationResistanceQueryEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, IsEquippingTargetAttemptEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, IsUnequippingTargetAttemptEvent>(RelayInventoryEvent);
+ SubscribeLocalEvent<InventoryComponent, ChameleonControllerOutfitSelectedEvent>(RelayInventoryEvent);
// by-ref events
SubscribeLocalEvent<InventoryComponent, RefreshFrictionModifiersEvent>(RefRelayInventoryEvent);
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)
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;
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)
{
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;
+ }
}
--- /dev/null
+chameleon-outfit-sus-name = Sus
--- /dev/null
+chameleon-controller-ui-window-name = Chameleon controls
department-Science = Science
department-Silicon = Silicon
department-Specific = Station specific
+
+department-Unknown = Unknown
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
- id: ClothingEyesChameleon
- id: ClothingHeadsetChameleon
- id: ClothingShoesChameleon
- - id: BarberScissors
+ - id: ChameleonControllerImplanter
- type: entity
parent: ClothingBackpackDuffelSyndicateBundle
- type: Tag
tags:
- HudMedical
+ - WhitelistChameleon
- type: entity
parent: [ClothingEyesBase, ShowSecurityIcons, BaseSecurityContraband]
- type: Tag
tags:
- HudSecurity
+ - WhitelistChameleon
- type: entity
parent: [ClothingEyesBase, BaseCommandContraband]
- Bandana
- ClothMade
- Recyclable
+ - WhitelistChameleon
- type: entity
parent: [ClothingHeadBandBase, ClothingMaskBandBlack]
- type: Tag
tags:
- Medal
+ - WhitelistChameleon
- type: entity
parent: ClothingNeckBase
- type: Tag
tags:
- Medal
+ - WhitelistChameleon
- type: entity
parent: ClothingNeckBase
- type: Tag
tags:
- Medal
+ - WhitelistChameleon
- type: entity
parent: ClothingNeckBase
- type: Tag
tags:
- Medal
+ - WhitelistChameleon
- type: entity
parent: ClothingNeckBase
- type: Tag
tags:
- Medal
+ - WhitelistChameleon
- type: entity
parent: ClothingNeckBase
- type: Tag
tags:
- Medal
+ - WhitelistChameleon
- type: entity
parent: ClothingNeckBase
- type: Tag
tags:
- Medal
+ - WhitelistChameleon
- type: entity
parent: ClothingNeckBase
- type: Tag
tags:
- Medal
+ - WhitelistChameleon
- type: Tag
tags:
- MonkeyWearable
+ - WhitelistChameleon
#Wizard Hardsuit
- type: entity
- type: Tag
tags:
- HiViz
+ - WhitelistChameleon
#(Bartender) vest
- type: entity
- type: Implanter
implant: DnaScramblerImplant
+- type: entity
+ id: ChameleonControllerImplanter
+ suffix: chameleon controller
+ parent: BaseImplantOnlyImplanterSyndi
+ components:
+ - type: Implanter
+ implant: ChameleonControllerImplant
+
#Nuclear Operative/Special implanters
- type: entity
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
- Wirecutter
- Welder
- Multitool
+
+- type: chameleonOutfit
+ id: NinjaChameleonOutfit
+ name: roles-antag-space-ninja-name
+ startingGear: SpaceNinjaGear
+ equipment:
+ id: PassengerPDA
+ neck: ClothingNeckGoldmedal
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
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: CargoTechnicianChameleonOutfit
+ job: CargoTechnician
+ equipment:
+ head: ClothingHeadHatCargosoft
+ eyes: ClothingEyesGlassesCheapSunglasses
+ mask: ClothingMaskBreath
+ outerClothing: ClothingOuterWinterCargo
+ neck: ClothingNeckScarfStripedBrown
+ gloves: ClothingHandsGlovesFingerless
storage:
back:
- Flash
+
+- type: chameleonOutfit
+ id: QuartermasterChameleonOutfit
+ job: Quartermaster
+ hasMindShield: true
+ equipment:
+ head: ClothingHeadHatQMsoft
+ eyes: ClothingEyesGlassesSunglasses
+ mask: ClothingMaskBreath
+ outerClothing: ClothingOuterWinterQM
+ neck: ClothingNeckCloakQm
+ gloves: ClothingHandsKnuckleDustersQM
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: SalvageSpecialistChameleonOutfit
+ job: SalvageSpecialist
+ equipment:
+ head: ClothingHeadHatCargosoft
+ eyes: ClothingEyesGlassesCheapSunglasses
+ mask: ClothingMaskGasExplorer
+ outerClothing: ClothingOuterWinterMiner
+ neck: ClothingNeckScarfStripedBrown
+ gloves: ClothingHandsGlovesColorBlack
+
- GrenadeFlashBang
- PillAmbuzolPlus
- PillAmbuzol
+
+- type: chameleonOutfit
+ id: CBURNChameleonOutfit
+ job: CBURN
+ hasMindShield: true
+ equipment:
+ head: ClothingHeadHelmetCBURN
+ neck: ClothingNeckScarfStripedBrown
- FreedomImplanter
inhand:
- WeaponPulseRifle
+
+- type: chameleonOutfit
+ id: DeathSquadChameleonOutfit
+ job: DeathSquad
+ hasMindShield: true
+ equipment:
+ head: ClothingHeadHelmetHardsuitDeathsquad
+ neck: ClothingNeckBronzeheart
- 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
belt: WeaponPistolN1984
pocket1: BoxFolderBlack
pocket2: PenCentcom
+
+
+- type: chameleonOutfit
+ id: CentralCommandOfficialOutfit
+ job: CentralCommandOfficial
+ hasMindShield: true
+ equipment:
+ neck: ClothingNeckScarfStripedCentcom
+ mask: ClothingMaskGasCentcom
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: PassengerChameleonOutfit
+ job: Passenger
+ equipment:
+ head: ClothingHeadHatWelding
+ eyes: ClothingEyesGlassesCheapSunglasses
+ mask: ClothingMaskGas
+ neck: ClothingNeckMantle
+ outerClothing: ClothingOuterWinterCoat
+ gloves: ClothingHandsGlovesColorYellowBudget
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: BartenderChameleonOutfit
+ job: Bartender
+ equipment:
+ head: ClothingHeadHatTophat
+ eyes: ClothingEyesGlassesSunglasses
+ mask: ClothingMaskBreath
+ neck: ClothingNeckScarfStripedBlack
+ outerClothing: ClothingOuterArmorBasicSlim
+ gloves: ClothingHandsGlovesColorBlack
+
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: BotanistChameleonOutfit
+ job: Botanist
+ equipment:
+ head: ClothingHeadBandBotany
+ eyes: ClothingEyesGlassesCheapSunglasses
+ mask: ClothingMaskBreath
+ neck: ClothingNeckScarfStripedGreen
+ outerClothing: ClothingOuterApronBotanist
+ gloves: ClothingHandsGlovesLeather
back:
- Bible
- RubberStampChaplain
+
+- type: chameleonOutfit
+ id: ChaplainChameleonOutfit
+ job: Chaplain
+ equipment:
+ head: ClothingHeadHatPlaguedoctor
+ eyes: ClothingEyesGlasses
+ mask: ClothingMaskPlague
+ neck: ClothingNeckStoleChaplain
+ outerClothing: ClothingOuterPlagueSuit
+ gloves: ClothingHandsGlovesColorBlack
+
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: ChefChameleonOutfit
+ job: Chef
+ equipment:
+ head: ClothingHeadHatChef
+ eyes: ClothingEyesGlassesCheapSunglasses
+ mask: ClothingMaskItalianMoustache
+ neck: ClothingNeckScarfStripedBrown
+ outerClothing: ClothingOuterJacketChef
+ gloves: ClothingHandsGlovesColorWhite
+ shoes: ClothingShoesChef
back:
- RubberStampClown
- CrayonRainbow
+
+- type: chameleonOutfit
+ id: ClownChameleonOutfit
+ job: Clown
+ equipment:
+ head: ClothingHeadHatXmasCrown
+ eyes: ClothingEyesGlassesCheapSunglasses
+ neck: ClothingHeadHatFlowerWreath
+ outerClothing: ClothingOuterClownPriest
+ gloves: ClothingHandsGlovesColorYellowBudget
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
back:
- RubberStampLawyer
- BookSpaceLaw
+
+- type: chameleonOutfit
+ id: LawyerChameleonOutfit
+ job: Lawyer
+ equipment:
+ head: ClothingHeadHatBowlerHat
+ eyes: ClothingEyesGlassesCheapSunglasses
+ mask: ClothingMaskBreath
+ neck: ClothingNeckLawyerbadge
+ outerClothing: ClothingOuterWinterColorBlack
+ gloves: ClothingHandsGlovesColorBlack
storage:
back:
- BookRandom
+
+- type: chameleonOutfit
+ id: LibrarianChameleonOutfit
+ job: Librarian
+ equipment:
+ head: ClothingHeadHatCanadaBeanie
+ eyes: ClothingEyesGlassesJamjar
+ mask: ClothingMaskGas
+ neck: ClothingNeckScarfStripedGreen
+ outerClothing: ClothingOuterWinterColorGreen
+ gloves: ClothingHandsGlovesColorBlack
state: full
- type: InstantAction
event: !type:InvisibleWallActionEvent
+
+- type: chameleonOutfit
+ id: MimeChameleonOutfit
+ job: Mime
+ equipment:
+ head: ClothingHeadHatMimesoft
+ eyes: ClothingEyesGlassesCheapSunglasses
+ neck: ClothingNeckScarfStripedZebra
+ outerClothing: ClothingOuterWinterMime
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: MusicianChameleonOutfit
+ job: Musician
+ equipment:
+ head: ClothingHeadHatTophat
+ mask: ClothingMaskBreath
+ neck: ClothingNeckHeadphones
+ outerClothing: ClothingOuterWinterMusician
+ gloves: ClothingHandsGlovesColorBlack
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: ServiceWorkerChameleonOutfit
+ job: ServiceWorker
+ equipment:
+ head: ClothingHeadHatBowlerHat
+ eyes: ClothingEyesGlassesSunglasses
+ mask: ClothingMaskGas
+ neck: ClothingNeckScarfStripedBrown
+ outerClothing: ClothingOuterVest
+ gloves: ClothingHandsGlovesColorBlack
back:
- Flash
# - StationCharter
+
+- type: chameleonOutfit
+ id: CaptainChameleonOutfit
+ job: Captain
+ hasMindShield: true
+ equipment:
+ head: ClothingHeadHatCapcap
+ eyes: ClothingEyesGlassesCommand
+ mask: ClothingMaskGasCaptain
+ neck: ClothingNeckCloakCap
storage:
back:
- Flash
+
+- type: chameleonOutfit
+ id: HeadOfPersonnelChameleonOutfit
+ job: HeadOfPersonnel
+ hasMindShield: true
+ equipment:
+ head: ClothingHeadHatHopcap
+ eyes: ClothingEyesHudCommand
+ mask: ClothingMaskNeckGaiterRed
+ neck: ClothingNeckCloakHop
+ outerClothing: ClothingOuterWinterHoP
+
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: AtmosphericTechnicianChameleonOutfit
+ job: AtmosphericTechnician
+ equipment:
+ head: ClothingHeadHelmetAtmosFire
+ mask: ClothingMaskGasAtmos
+ neck: ClothingNeckScarfStripedLightBlue
+ outerClothing: ClothingOuterSuitAtmosFire
+ gloves: ClothingHandsGlovesColorYellow
storage:
back:
- Flash
+
+- type: chameleonOutfit
+ id: ChiefEngineerChameleonOutfit
+ job: ChiefEngineer
+ hasMindShield: true
+ equipment:
+ head: ClothingHeadHatBeretEngineering
+ mask: ClothingMaskBreath
+ neck: ClothingNeckCloakCe
+ outerClothing: ClothingOuterWinterCE
+ gloves: ClothingHandsGlovesColorYellow
+ shoes: ClothingShoesBootsMagAdv
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: StationEngineerChameleonOutfit
+ job: StationEngineer
+ equipment:
+ head: ClothingHeadHatHardhatYellow
+ mask: ClothingMaskBreath
+ neck: ClothingNeckScarfStripedOrange
+ outerClothing: ClothingOuterWinterEngi
+ gloves: ClothingHandsGlovesColorYellow
+ shoes: ClothingShoesBootsMag
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: TechnicalAssistantChameleonOutfit
+ job: TechnicalAssistant
+ equipment:
+ head: ClothingHeadHatHardhatOrange
+ eyes: ClothingEyesGlassesMeson
+ mask: ClothingMaskBreath
+ neck: ClothingNeckScarfStripedOrange
+ outerClothing: ClothingOuterVestHazard
+ gloves: ClothingHandsGlovesColorYellow
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
--- /dev/null
+- 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
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
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: ChemistChameleonOutfit
+ job: Chemist
+ equipment:
+ head: ClothingHeadHatBeretMedic
+ mask: ClothingMaskSterile
+ neck: ClothingNeckStethoscope
+ outerClothing: ClothingOuterCoatLabChem
+ gloves: ClothingHandsGlovesLatex
storage:
back:
- Flash
+
+- type: chameleonOutfit
+ id: ChiefMedicalOfficerChameleonOutfit
+ job: ChiefMedicalOfficer
+ hasMindShield: true
+ equipment:
+ head: ClothingHeadHatBeretCmo
+ eyes: ClothingEyesHudMedical
+ mask: ClothingMaskSterile
+ neck: ClothingCloakCmo
+ outerClothing: ClothingOuterCoatLabCmo
+ gloves: ClothingHandsGlovesNitrile
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: MedicalDoctorChameleonOutfit
+ job: MedicalDoctor
+ equipment:
+ head: ClothingHeadNurseHat
+ eyes: ClothingEyesHudMedical
+ mask: ClothingMaskSterile
+ neck: ClothingNeckStethoscope
+ outerClothing: ClothingOuterCoatLab
+ gloves: ClothingHandsGlovesLatex
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: MedicalInternChameleonOutfit
+ job: MedicalIntern
+ equipment:
+ head: ClothingHeadHatBeretMedic
+ eyes: ClothingEyesHudMedical
+ mask: ClothingMaskSterile
+ neck: ClothingNeckStethoscope
+ outerClothing: ClothingOuterCoatLabOpened
+ gloves: ClothingHandsGlovesLatex
storage:
back:
- EmergencyRollerBedSpawnFolded
+
+- type: chameleonOutfit
+ id: ParamedicChameleonOutfit
+ job: Paramedic
+ equipment:
+ head: ClothingHeadHatParamedicsoft
+ eyes: ClothingEyesHudMedical
+ mask: ClothingMaskSterile
+ neck: ClothingNeckStethoscope
+ outerClothing: ClothingOuterCoatParamedicWB
+ gloves: ClothingHandsGlovesLatex
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: ResearchAssistantChameleonOutfit
+ job: ResearchAssistant
+ equipment:
+ head: ClothingHeadHatCardborg
+ eyes: ClothingEyesGlasses
+ mask: ClothingMaskGas
+ neck: ClothingNeckScarfStripedPurple
+ outerClothing: ClothingOuterCoatLab
+ gloves: ClothingHandsGlovesLatex
storage:
back:
- Flash
+
+- type: chameleonOutfit
+ id: ResearchDirectorChameleonOutfit
+ job: ResearchDirector
+ hasMindShield: true
+ equipment:
+ head: ClothingHeadHatBeretRND
+ eyes: ClothingEyesGlasses
+ mask: ClothingMaskGas
+ neck: ClothingNeckCloakRd
+ outerClothing: ClothingOuterCoatRD
+ gloves: ClothingHandsGlovesColorPurple
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: ScientistChameleonOutfit
+ job: Scientist
+ equipment:
+ head: ClothingHeadBandSkull
+ eyes: ClothingEyesGlassesCheapSunglasses
+ mask: ClothingMaskGas
+ neck: ClothingNeckTieSci
+ outerClothing: ClothingOuterCoatRnd
+ gloves: ClothingHandsGlovesLatex
- Flash
- ForensicPad
- ForensicScanner
+
+- type: chameleonOutfit
+ id: DetectiveChameleonOutfit
+ job: Detective
+ hasMindShield: true
+ equipment:
+ head: ClothingHeadHatFedoraBrown
+ mask: ClothingMaskGasSecurity
+ neck: ClothingNeckTieDet
+ gloves: ClothingHandsGlovesForensic
back:
- Flash
- MagazinePistol
+
+- type: chameleonOutfit
+ id: HeadOfSecurityChameleonOutfit
+ job: HeadOfSecurity
+ hasMindShield: true
+ equipment:
+ mask: ClothingMaskGasSecurity
+ neck: ClothingNeckCloakHos
back:
- Flash
- MagazinePistol
+
+- type: chameleonOutfit
+ id: SecurityCadetChameleonOutfit
+ job: SecurityCadet
+ hasMindShield: true
+ equipment:
+ head: ClothingHeadHelmetBasic
+ eyes: ClothingEyesHudSecurity
+ mask: ClothingMaskGasSecurity
+ neck: ClothingNeckScarfStripedRed
+ belt: ClothingBeltSecurity
+ gloves: ClothingHandsGlovesColorBlack
back:
- Flash
- MagazinePistol
+
+- type: chameleonOutfit
+ id: SecurityOfficerChameleonOutfit
+ job: SecurityOfficer
+ hasMindShield: true
+ equipment:
+ head: ClothingHeadHatBeretSecurity
+ mask: ClothingMaskGasSecurity
+ neck: Dinkystar
+ gloves: ClothingHandsGlovesColorBlack
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
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: BoxerChameleonOutfit
+ job: Boxer
+ equipment:
+ head: ClothingHeadHatBlacksoft
+ eyes: ClothingEyesGlassesCheapSunglasses
+ mask: ClothingMaskBreath
+ outerClothing: ClothingOuterWinterColorGray
+ jumpsuit: UniformShortsRedWithTop
+ neck: ClothingNeckScarfStripedRed
storage:
back:
- RubberStampPsychologist
+
+- type: chameleonOutfit
+ id: PsychologistChameleonOutfit
+ job: Psychologist
+ equipment:
+ head: ClothingHeadHatBeretMedic
+ eyes: ClothingEyesHudMedical
+ mask: ClothingMaskSterile
+ outerClothing: ClothingOuterCoatLab
+ neck: ClothingNeckStethoscope
+ belt: ClothingBeltMedical
+ gloves: ClothingHandsGlovesLatex
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: ReporterChameleonOutfit
+ job: Reporter
+ equipment:
+ head: ClothingHeadHatFedoraGrey
+ eyes: ClothingEyesGlassesCheapSunglasses
+ mask: ClothingMaskBreath
+ outerClothing: ClothingOuterCoatTrench
+ neck: ClothingNeckTieRed
+ gloves: ClothingHandsGlovesFingerless
#storage:
#back:
#- Stuff
+
+- type: chameleonOutfit
+ id: ZookeeperChameleonOutfit
+ job: Zookeeper
+ equipment:
+ eyes: ClothingEyesGlassesCheapSunglasses
+ mask: ClothingMaskBreath
+ outerClothing: ClothingOuterWinterColorLightBrown
+ neck: ClothingNeckScarfStripedBrown
+ belt: ClothingBeltStorageWaistbag
+ gloves: ClothingHandsGlovesColorBrown
+
- type: Tag
id: MimeHappyHonk
+- type: Tag
+ id: FakeMindShieldImplant
+
- type: Tag
id: MindTransferTarget
{
"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
},
{
"name": "explosive"
+ },
+ {
+ "name": "chameleon"
}
]
}