Chameleon controller implant (Clothing fast switch) (#33887)

* 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>
This commit is contained in:
beck-thompson
2025-05-30 03:07:25 -07:00
committed by GitHub
parent fa3468270e
commit 05fac53de6
79 changed files with 1437 additions and 3 deletions

View File

@@ -0,0 +1,5 @@
using Content.Shared.Implants;
namespace Content.Client.Implants;
public sealed partial class ChameleonControllerSystem : SharedChameleonControllerSystem;

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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));
}
}

View File

@@ -9,6 +9,11 @@ using Robust.Server.GameObjects;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Content.Shared.Roles; using Content.Shared.Roles;
using System.Diagnostics.CodeAnalysis; 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 namespace Content.Server.Access.Systems
{ {
@@ -18,6 +23,8 @@ namespace Content.Server.Access.Systems
[Dependency] private readonly IdCardSystem _cardSystem = default!; [Dependency] private readonly IdCardSystem _cardSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ChameleonClothingSystem _chameleon = default!;
[Dependency] private readonly ChameleonControllerSystem _chamController = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -28,6 +35,46 @@ namespace Content.Server.Access.Systems
SubscribeLocalEvent<AgentIDCardComponent, AgentIDCardNameChangedMessage>(OnNameChanged); SubscribeLocalEvent<AgentIDCardComponent, AgentIDCardNameChangedMessage>(OnNameChanged);
SubscribeLocalEvent<AgentIDCardComponent, AgentIDCardJobChangedMessage>(OnJobChanged); SubscribeLocalEvent<AgentIDCardComponent, AgentIDCardJobChangedMessage>(OnJobChanged);
SubscribeLocalEvent<AgentIDCardComponent, AgentIDCardJobIconChangedMessage>(OnJobIconChanged); 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) private void OnAfterInteract(EntityUid uid, AgentIDCardComponent component, AfterInteractEvent args)

View File

@@ -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;
}
}

View File

@@ -11,6 +11,8 @@ using Content.Shared.Access.Components;
using Content.Shared.CartridgeLoader; using Content.Shared.CartridgeLoader;
using Content.Shared.Chat; using Content.Shared.Chat;
using Content.Shared.DeviceNetwork.Components; using Content.Shared.DeviceNetwork.Components;
using Content.Shared.Implants;
using Content.Shared.Inventory;
using Content.Shared.Light; using Content.Shared.Light;
using Content.Shared.Light.EntitySystems; using Content.Shared.Light.EntitySystems;
using Content.Shared.PDA; using Content.Shared.PDA;
@@ -56,6 +58,14 @@ namespace Content.Server.PDA
SubscribeLocalEvent<StationRenamedEvent>(OnStationRenamed); SubscribeLocalEvent<StationRenamedEvent>(OnStationRenamed);
SubscribeLocalEvent<EntityRenamedEvent>(OnEntityRenamed, after: new[] { typeof(IdCardSystem) }); SubscribeLocalEvent<EntityRenamedEvent>(OnEntityRenamed, after: new[] { typeof(IdCardSystem) });
SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertLevelChanged); 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) private void OnEntityRenamed(ref EntityRenamedEvent ev)

View File

@@ -204,6 +204,22 @@ public abstract class SharedIdCardSystem : EntitySystem
return true; 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> /// <summary>
/// Attempts to change the full name of a card. /// Attempts to change the full name of a card.
/// Returns true/false. /// Returns true/false.

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -12,6 +12,7 @@ using Content.Shared.Explosion;
using Content.Shared.Eye.Blinding.Systems; using Content.Shared.Eye.Blinding.Systems;
using Content.Shared.Gravity; using Content.Shared.Gravity;
using Content.Shared.IdentityManagement.Components; using Content.Shared.IdentityManagement.Components;
using Content.Shared.Implants;
using Content.Shared.Inventory.Events; using Content.Shared.Inventory.Events;
using Content.Shared.Movement.Events; using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
@@ -51,6 +52,7 @@ public partial class InventorySystem
SubscribeLocalEvent<InventoryComponent, ZombificationResistanceQueryEvent>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, ZombificationResistanceQueryEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, IsEquippingTargetAttemptEvent>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, IsEquippingTargetAttemptEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, IsUnequippingTargetAttemptEvent>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, IsUnequippingTargetAttemptEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, ChameleonControllerOutfitSelectedEvent>(RelayInventoryEvent);
// by-ref events // by-ref events
SubscribeLocalEvent<InventoryComponent, RefreshFrictionModifiersEvent>(RefRelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, RefreshFrictionModifiersEvent>(RefRelayInventoryEvent);

View File

@@ -1,14 +1,27 @@
using Content.Shared.Actions; using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Implants;
using Content.Shared.Mindshield.Components; using Content.Shared.Mindshield.Components;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Shared.Mindshield.FakeMindShield; namespace Content.Shared.Mindshield.FakeMindShield;
public sealed class SharedFakeMindShieldSystem : EntitySystem 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() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<FakeMindShieldComponent, FakeMindShieldToggleEvent>(OnToggleMindshield); SubscribeLocalEvent<FakeMindShieldComponent, FakeMindShieldToggleEvent>(OnToggleMindshield);
SubscribeLocalEvent<FakeMindShieldComponent, ChameleonControllerOutfitSelectedEvent>(OnChameleonControllerOutfitSelected);
} }
private void OnToggleMindshield(EntityUid uid, FakeMindShieldComponent comp, FakeMindShieldToggleEvent toggleEvent) private void OnToggleMindshield(EntityUid uid, FakeMindShieldComponent comp, FakeMindShieldToggleEvent toggleEvent)
@@ -16,6 +29,48 @@ public sealed class SharedFakeMindShieldSystem : EntitySystem
comp.IsEnabled = !comp.IsEnabled; comp.IsEnabled = !comp.IsEnabled;
Dirty(uid, comp); 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; public sealed partial class FakeMindShieldToggleEvent : InstantActionEvent;

View File

@@ -100,6 +100,45 @@ public abstract class SharedJobSystem : EntitySystem
return false; 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) public bool MindHasJobWithId(EntityUid? mindId, string prototypeId)
{ {

View File

@@ -179,4 +179,34 @@ public abstract class SharedStationSpawningSystem : EntitySystem
RaiseLocalEvent(entity, ref ev); 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;
}
} }

View File

@@ -0,0 +1 @@
chameleon-outfit-sus-name = Sus

View File

@@ -0,0 +1 @@
chameleon-controller-ui-window-name = Chameleon controls

View File

@@ -8,3 +8,5 @@ department-Security = Security
department-Science = Science department-Science = Science
department-Silicon = Silicon department-Silicon = Silicon
department-Specific = Station specific department-Specific = Station specific
department-Unknown = Unknown

View File

@@ -382,6 +382,9 @@
useDelay: 1 useDelay: 1
- type: InstantAction - type: InstantAction
event: !type:FakeMindShieldToggleEvent event: !type:FakeMindShieldToggleEvent
- type: Tag
tags:
- FakeMindShieldImplant
- type: entity - type: entity
parent: BaseToggleAction parent: BaseToggleAction
@@ -395,3 +398,15 @@
state: icon-siren state: icon-siren
useDelay: 1 useDelay: 1
itemIconStyle: BigAction 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

View File

@@ -211,7 +211,7 @@
- id: ClothingEyesChameleon - id: ClothingEyesChameleon
- id: ClothingHeadsetChameleon - id: ClothingHeadsetChameleon
- id: ClothingShoesChameleon - id: ClothingShoesChameleon
- id: BarberScissors - id: ChameleonControllerImplanter
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicateBundle parent: ClothingBackpackDuffelSyndicateBundle

View File

@@ -44,6 +44,7 @@
- type: Tag - type: Tag
tags: tags:
- HudMedical - HudMedical
- WhitelistChameleon
- type: entity - type: entity
parent: [ClothingEyesBase, ShowSecurityIcons, BaseSecurityContraband] parent: [ClothingEyesBase, ShowSecurityIcons, BaseSecurityContraband]
@@ -58,6 +59,7 @@
- type: Tag - type: Tag
tags: tags:
- HudSecurity - HudSecurity
- WhitelistChameleon
- type: entity - type: entity
parent: [ClothingEyesBase, BaseCommandContraband] parent: [ClothingEyesBase, BaseCommandContraband]

View File

@@ -29,6 +29,7 @@
- Bandana - Bandana
- ClothMade - ClothMade
- Recyclable - Recyclable
- WhitelistChameleon
- type: entity - type: entity
parent: [ClothingHeadBandBase, ClothingMaskBandBlack] parent: [ClothingHeadBandBase, ClothingMaskBandBlack]

View File

@@ -12,6 +12,7 @@
- type: Tag - type: Tag
tags: tags:
- Medal - Medal
- WhitelistChameleon
- type: entity - type: entity
parent: ClothingNeckBase parent: ClothingNeckBase
@@ -28,6 +29,7 @@
- type: Tag - type: Tag
tags: tags:
- Medal - Medal
- WhitelistChameleon
- type: entity - type: entity
parent: ClothingNeckBase parent: ClothingNeckBase
@@ -42,6 +44,7 @@
- type: Tag - type: Tag
tags: tags:
- Medal - Medal
- WhitelistChameleon
- type: entity - type: entity
parent: ClothingNeckBase parent: ClothingNeckBase
@@ -56,6 +59,7 @@
- type: Tag - type: Tag
tags: tags:
- Medal - Medal
- WhitelistChameleon
- type: entity - type: entity
parent: ClothingNeckBase parent: ClothingNeckBase
@@ -70,6 +74,7 @@
- type: Tag - type: Tag
tags: tags:
- Medal - Medal
- WhitelistChameleon
- type: entity - type: entity
parent: ClothingNeckBase parent: ClothingNeckBase
@@ -84,6 +89,7 @@
- type: Tag - type: Tag
tags: tags:
- Medal - Medal
- WhitelistChameleon
- type: entity - type: entity
parent: ClothingNeckBase parent: ClothingNeckBase
@@ -98,6 +104,7 @@
- type: Tag - type: Tag
tags: tags:
- Medal - Medal
- WhitelistChameleon
- type: entity - type: entity
parent: ClothingNeckBase parent: ClothingNeckBase
@@ -114,3 +121,4 @@
- type: Tag - type: Tag
tags: tags:
- Medal - Medal
- WhitelistChameleon

View File

@@ -665,6 +665,7 @@
- type: Tag - type: Tag
tags: tags:
- MonkeyWearable - MonkeyWearable
- WhitelistChameleon
#Wizard Hardsuit #Wizard Hardsuit
- type: entity - type: entity

View File

@@ -31,6 +31,7 @@
- type: Tag - type: Tag
tags: tags:
- HiViz - HiViz
- WhitelistChameleon
#(Bartender) vest #(Bartender) vest
- type: entity - type: entity

View File

@@ -246,6 +246,14 @@
- type: Implanter - type: Implanter
implant: DnaScramblerImplant implant: DnaScramblerImplant
- type: entity
id: ChameleonControllerImplanter
suffix: chameleon controller
parent: BaseImplantOnlyImplanterSyndi
components:
- type: Implanter
implant: ChameleonControllerImplant
#Nuclear Operative/Special implanters #Nuclear Operative/Special implanters
- type: entity - type: entity

View File

@@ -212,6 +212,23 @@
components: components:
- HumanoidAppearance # syndies cant turn hamlet into a human - 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 #Nuclear Operative/Special Exclusive implants
- type: entity - type: entity

View File

@@ -33,3 +33,11 @@
- Wirecutter - Wirecutter
- Welder - Welder
- Multitool - Multitool
- type: chameleonOutfit
id: NinjaChameleonOutfit
name: roles-antag-space-ninja-name
startingGear: SpaceNinjaGear
equipment:
id: PassengerPDA
neck: ClothingNeckGoldmedal

View File

@@ -65,6 +65,15 @@
equipment: equipment:
pocket2: BaseUplinkRadio40TC 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 #Nuclear Operative Commander Gear
- type: startingGear - type: startingGear
id: SyndicateCommanderGearFull id: SyndicateCommanderGearFull
@@ -75,6 +84,15 @@
inhand: inhand:
- NukeOpsDeclarationOfWar - 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 #Nuclear Operative Medic Gear
- type: startingGear - type: startingGear
id: SyndicateOperativeMedicFull id: SyndicateOperativeMedicFull
@@ -97,6 +115,15 @@
- CombatMedipen - CombatMedipen
- DeathAcidifierImplanter - DeathAcidifierImplanter
- type: chameleonOutfit
id: NukeopsMedicOutfit
name: roles-antag-nuclear-operative-agent-name
startingGear: SyndicateOperativeMedicFull
icon: "JobIconSyndicate"
equipment:
head: ClothingHeadHelmetHardsuitSyndieMedic
neck: ClothingNeckScarfStripedLightBlue
#Lone Operative Gear #Lone Operative Gear
- type: startingGear - type: startingGear
id: SyndicateLoneOperativeGearFull id: SyndicateLoneOperativeGearFull

View File

@@ -25,3 +25,14 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: CargoTechnicianChameleonOutfit
job: CargoTechnician
equipment:
head: ClothingHeadHatCargosoft
eyes: ClothingEyesGlassesCheapSunglasses
mask: ClothingMaskBreath
outerClothing: ClothingOuterWinterCargo
neck: ClothingNeckScarfStripedBrown
gloves: ClothingHandsGlovesFingerless

View File

@@ -44,3 +44,15 @@
storage: storage:
back: back:
- Flash - Flash
- type: chameleonOutfit
id: QuartermasterChameleonOutfit
job: Quartermaster
hasMindShield: true
equipment:
head: ClothingHeadHatQMsoft
eyes: ClothingEyesGlassesSunglasses
mask: ClothingMaskBreath
outerClothing: ClothingOuterWinterQM
neck: ClothingNeckCloakQm
gloves: ClothingHandsKnuckleDustersQM

View File

@@ -25,3 +25,15 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: SalvageSpecialistChameleonOutfit
job: SalvageSpecialist
equipment:
head: ClothingHeadHatCargosoft
eyes: ClothingEyesGlassesCheapSunglasses
mask: ClothingMaskGasExplorer
outerClothing: ClothingOuterWinterMiner
neck: ClothingNeckScarfStripedBrown
gloves: ClothingHandsGlovesColorBlack

View File

@@ -38,3 +38,11 @@
- GrenadeFlashBang - GrenadeFlashBang
- PillAmbuzolPlus - PillAmbuzolPlus
- PillAmbuzol - PillAmbuzol
- type: chameleonOutfit
id: CBURNChameleonOutfit
job: CBURN
hasMindShield: true
equipment:
head: ClothingHeadHelmetCBURN
neck: ClothingNeckScarfStripedBrown

View File

@@ -45,3 +45,11 @@
- FreedomImplanter - FreedomImplanter
inhand: inhand:
- WeaponPulseRifle - WeaponPulseRifle
- type: chameleonOutfit
id: DeathSquadChameleonOutfit
job: DeathSquad
hasMindShield: true
equipment:
head: ClothingHeadHelmetHardsuitDeathsquad
neck: ClothingNeckBronzeheart

View File

@@ -91,6 +91,14 @@
- CrowbarRed - CrowbarRed
- MagazineMagnum - MagazineMagnum
- type: chameleonOutfit
id: ERTLeaderChameleonOutfit
job: ERTLeader
hasMindShield: true
equipment:
head: ClothingHeadHelmetHardsuitERTLeader
neck: ClothingNeckBronzeheart
# Chaplain # Chaplain
- type: job - type: job
id: ERTChaplain id: ERTChaplain
@@ -175,6 +183,16 @@
- FoodBakedBunHotX - FoodBakedBunHotX
- Lighter - Lighter
- type: chameleonOutfit
id: ERTChaplainChameleonOutfit
job: ERTChaplain
hasMindShield: true
equipment:
head: ClothingHeadHelmetHardsuitERTChaplain
mask: ClothingMaskGasERT
outerClothing: ClothingOuterHardsuitERTChaplain
shoes: ClothingShoesBootsMagAdv
# Engineer # Engineer
- type: job - type: job
id: ERTEngineer id: ERTEngineer
@@ -250,6 +268,14 @@
- SheetSteel - SheetSteel
- SheetGlass - SheetGlass
- type: chameleonOutfit
id: ERTEngineerChameleonOutfit
job: ERTEngineer
hasMindShield: true
equipment:
head: ClothingHeadHelmetHardsuitERTEngineer
neck: ClothingNeckEngineermedal
# Security # Security
- type: job - type: job
id: ERTSecurity id: ERTSecurity
@@ -257,7 +283,7 @@
description: job-description-ertsecurity description: job-description-ertsecurity
playTimeTracker: JobERTSecurity playTimeTracker: JobERTSecurity
setPreference: false setPreference: false
startingGear: ERTEngineerGearEVA startingGear: ERTSecurityGearEVA
icon: "JobIconNanotrasen" icon: "JobIconNanotrasen"
supervisors: job-supervisors-centcom supervisors: job-supervisors-centcom
canBeAntag: false canBeAntag: false
@@ -343,6 +369,14 @@
- CrowbarRed - CrowbarRed
- MagazinePistol - MagazinePistol
- type: chameleonOutfit
id: ERTSecurityChameleonOutfit
job: ERTSecurity
hasMindShield: true
equipment:
head: ClothingHeadHelmetHardsuitERTSecurity
neck: ClothingNeckSecuritymedal
# Medical # Medical
- type: job - type: job
id: ERTMedical id: ERTMedical
@@ -410,6 +444,14 @@
- ChemistryBottleEpinephrine - ChemistryBottleEpinephrine
- ChemistryBottleEpinephrine - ChemistryBottleEpinephrine
- type: chameleonOutfit
id: ERTMedicalChameleonOutfit
job: ERTMedical
hasMindShield: true
equipment:
head: ClothingHeadHelmetHardsuitERTMedical
neck: ClothingNeckStethoscope
# Janitor # Janitor
- type: job - type: job
id: ERTJanitor id: ERTJanitor
@@ -472,3 +514,12 @@
- Soap - Soap
- CrowbarRed - CrowbarRed
- AdvMopItem - AdvMopItem
- type: chameleonOutfit
id: ERTJanitorChameleonOutfit
job: ERTJanitor
hasMindShield: true
equipment:
head: ClothingHeadHelmetHardsuitERTJanitor
neck: ClothingNeckScarfStripedPurple
eyes: ClothingEyesGlassesSunglasses

View File

@@ -30,3 +30,12 @@
belt: WeaponPistolN1984 belt: WeaponPistolN1984
pocket1: BoxFolderBlack pocket1: BoxFolderBlack
pocket2: PenCentcom pocket2: PenCentcom
- type: chameleonOutfit
id: CentralCommandOfficialOutfit
job: CentralCommandOfficial
hasMindShield: true
equipment:
neck: ClothingNeckScarfStripedCentcom
mask: ClothingMaskGasCentcom

View File

@@ -17,3 +17,14 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: PassengerChameleonOutfit
job: Passenger
equipment:
head: ClothingHeadHatWelding
eyes: ClothingEyesGlassesCheapSunglasses
mask: ClothingMaskGas
neck: ClothingNeckMantle
outerClothing: ClothingOuterWinterCoat
gloves: ClothingHandsGlovesColorYellowBudget

View File

@@ -27,3 +27,15 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: BartenderChameleonOutfit
job: Bartender
equipment:
head: ClothingHeadHatTophat
eyes: ClothingEyesGlassesSunglasses
mask: ClothingMaskBreath
neck: ClothingNeckScarfStripedBlack
outerClothing: ClothingOuterArmorBasicSlim
gloves: ClothingHandsGlovesColorBlack

View File

@@ -28,3 +28,14 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: BotanistChameleonOutfit
job: Botanist
equipment:
head: ClothingHeadBandBotany
eyes: ClothingEyesGlassesCheapSunglasses
mask: ClothingMaskBreath
neck: ClothingNeckScarfStripedGreen
outerClothing: ClothingOuterApronBotanist
gloves: ClothingHandsGlovesLeather

View File

@@ -24,3 +24,15 @@
back: back:
- Bible - Bible
- RubberStampChaplain - RubberStampChaplain
- type: chameleonOutfit
id: ChaplainChameleonOutfit
job: Chaplain
equipment:
head: ClothingHeadHatPlaguedoctor
eyes: ClothingEyesGlasses
mask: ClothingMaskPlague
neck: ClothingNeckStoleChaplain
outerClothing: ClothingOuterPlagueSuit
gloves: ClothingHandsGlovesColorBlack

View File

@@ -28,3 +28,15 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: ChefChameleonOutfit
job: Chef
equipment:
head: ClothingHeadHatChef
eyes: ClothingEyesGlassesCheapSunglasses
mask: ClothingMaskItalianMoustache
neck: ClothingNeckScarfStripedBrown
outerClothing: ClothingOuterJacketChef
gloves: ClothingHandsGlovesColorWhite
shoes: ClothingShoesChef

View File

@@ -37,3 +37,13 @@
back: back:
- RubberStampClown - RubberStampClown
- CrayonRainbow - CrayonRainbow
- type: chameleonOutfit
id: ClownChameleonOutfit
job: Clown
equipment:
head: ClothingHeadHatXmasCrown
eyes: ClothingEyesGlassesCheapSunglasses
neck: ClothingHeadHatFlowerWreath
outerClothing: ClothingOuterClownPriest
gloves: ClothingHandsGlovesColorYellowBudget

View File

@@ -35,3 +35,14 @@
head: ClothingHeadHatCatEars head: ClothingHeadHatCatEars
ears: ClothingHeadsetService ears: ClothingHeadsetService
belt: ClothingBeltJanitorFilled belt: ClothingBeltJanitorFilled
- type: chameleonOutfit
id: JanitorChameleonOutfit
job: Janitor
equipment:
head: ClothingHeadHatPurplesoft
eyes: ClothingEyesGlassesCheapSunglasses
mask: ClothingMaskGas
neck: ClothingNeckScarfStripedPurple
outerClothing: ClothingOuterWinterJani
gloves: ClothingHandsGlovesJanitor

View File

@@ -27,3 +27,14 @@
back: back:
- RubberStampLawyer - RubberStampLawyer
- BookSpaceLaw - BookSpaceLaw
- type: chameleonOutfit
id: LawyerChameleonOutfit
job: Lawyer
equipment:
head: ClothingHeadHatBowlerHat
eyes: ClothingEyesGlassesCheapSunglasses
mask: ClothingMaskBreath
neck: ClothingNeckLawyerbadge
outerClothing: ClothingOuterWinterColorBlack
gloves: ClothingHandsGlovesColorBlack

View File

@@ -21,3 +21,14 @@
storage: storage:
back: back:
- BookRandom - BookRandom
- type: chameleonOutfit
id: LibrarianChameleonOutfit
job: Librarian
equipment:
head: ClothingHeadHatCanadaBeanie
eyes: ClothingEyesGlassesJamjar
mask: ClothingMaskGas
neck: ClothingNeckScarfStripedGreen
outerClothing: ClothingOuterWinterColorGreen
gloves: ClothingHandsGlovesColorBlack

View File

@@ -45,3 +45,12 @@
state: full state: full
- type: InstantAction - type: InstantAction
event: !type:InvisibleWallActionEvent event: !type:InvisibleWallActionEvent
- type: chameleonOutfit
id: MimeChameleonOutfit
job: Mime
equipment:
head: ClothingHeadHatMimesoft
eyes: ClothingEyesGlassesCheapSunglasses
neck: ClothingNeckScarfStripedZebra
outerClothing: ClothingOuterWinterMime

View File

@@ -24,3 +24,13 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: MusicianChameleonOutfit
job: Musician
equipment:
head: ClothingHeadHatTophat
mask: ClothingMaskBreath
neck: ClothingNeckHeadphones
outerClothing: ClothingOuterWinterMusician
gloves: ClothingHandsGlovesColorBlack

View File

@@ -27,3 +27,14 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: ServiceWorkerChameleonOutfit
job: ServiceWorker
equipment:
head: ClothingHeadHatBowlerHat
eyes: ClothingEyesGlassesSunglasses
mask: ClothingMaskGas
neck: ClothingNeckScarfStripedBrown
outerClothing: ClothingOuterVest
gloves: ClothingHandsGlovesColorBlack

View File

@@ -46,3 +46,13 @@
back: back:
- Flash - Flash
# - StationCharter # - StationCharter
- type: chameleonOutfit
id: CaptainChameleonOutfit
job: Captain
hasMindShield: true
equipment:
head: ClothingHeadHatCapcap
eyes: ClothingEyesGlassesCommand
mask: ClothingMaskGasCaptain
neck: ClothingNeckCloakCap

View File

@@ -69,3 +69,15 @@
storage: storage:
back: back:
- Flash - Flash
- type: chameleonOutfit
id: HeadOfPersonnelChameleonOutfit
job: HeadOfPersonnel
hasMindShield: true
equipment:
head: ClothingHeadHatHopcap
eyes: ClothingEyesHudCommand
mask: ClothingMaskNeckGaiterRed
neck: ClothingNeckCloakHop
outerClothing: ClothingOuterWinterHoP

View File

@@ -30,3 +30,13 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: AtmosphericTechnicianChameleonOutfit
job: AtmosphericTechnician
equipment:
head: ClothingHeadHelmetAtmosFire
mask: ClothingMaskGasAtmos
neck: ClothingNeckScarfStripedLightBlue
outerClothing: ClothingOuterSuitAtmosFire
gloves: ClothingHandsGlovesColorYellow

View File

@@ -44,3 +44,15 @@
storage: storage:
back: back:
- Flash - Flash
- type: chameleonOutfit
id: ChiefEngineerChameleonOutfit
job: ChiefEngineer
hasMindShield: true
equipment:
head: ClothingHeadHatBeretEngineering
mask: ClothingMaskBreath
neck: ClothingNeckCloakCe
outerClothing: ClothingOuterWinterCE
gloves: ClothingHandsGlovesColorYellow
shoes: ClothingShoesBootsMagAdv

View File

@@ -26,3 +26,14 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: StationEngineerChameleonOutfit
job: StationEngineer
equipment:
head: ClothingHeadHatHardhatYellow
mask: ClothingMaskBreath
neck: ClothingNeckScarfStripedOrange
outerClothing: ClothingOuterWinterEngi
gloves: ClothingHandsGlovesColorYellow
shoes: ClothingShoesBootsMag

View File

@@ -30,3 +30,14 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: TechnicalAssistantChameleonOutfit
job: TechnicalAssistant
equipment:
head: ClothingHeadHatHardhatOrange
eyes: ClothingEyesGlassesMeson
mask: ClothingMaskBreath
neck: ClothingNeckScarfStripedOrange
outerClothing: ClothingOuterVestHazard
gloves: ClothingHandsGlovesColorYellow

View File

@@ -14,3 +14,14 @@
equipment: equipment:
pocket1: BikeHorn 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

View File

@@ -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

View File

@@ -28,3 +28,13 @@
jumpsuit: ClothingUniformJumpsuitColorPurple jumpsuit: ClothingUniformJumpsuitColorPurple
head: ClothingHeadHatVioletwizard head: ClothingHeadHatVioletwizard
outerClothing: ClothingOuterWizardViolet outerClothing: ClothingOuterWizardViolet
- type: chameleonOutfit
id: WizardChameleonOutfit
name: roles-antag-wizard-name
startingGear: WizardBlueGear
equipment:
eyes: ClothingEyesGlassesCheapSunglasses
mask: ClothingMaskBreath
neck: ClothingNeckLGBTPin
gloves: ClothingHandsGlovesColorBlack

View File

@@ -26,3 +26,13 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: ChemistChameleonOutfit
job: Chemist
equipment:
head: ClothingHeadHatBeretMedic
mask: ClothingMaskSterile
neck: ClothingNeckStethoscope
outerClothing: ClothingOuterCoatLabChem
gloves: ClothingHandsGlovesLatex

View File

@@ -44,3 +44,15 @@
storage: storage:
back: back:
- Flash - Flash
- type: chameleonOutfit
id: ChiefMedicalOfficerChameleonOutfit
job: ChiefMedicalOfficer
hasMindShield: true
equipment:
head: ClothingHeadHatBeretCmo
eyes: ClothingEyesHudMedical
mask: ClothingMaskSterile
neck: ClothingCloakCmo
outerClothing: ClothingOuterCoatLabCmo
gloves: ClothingHandsGlovesNitrile

View File

@@ -28,3 +28,14 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: MedicalDoctorChameleonOutfit
job: MedicalDoctor
equipment:
head: ClothingHeadNurseHat
eyes: ClothingEyesHudMedical
mask: ClothingMaskSterile
neck: ClothingNeckStethoscope
outerClothing: ClothingOuterCoatLab
gloves: ClothingHandsGlovesLatex

View File

@@ -27,3 +27,14 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: MedicalInternChameleonOutfit
job: MedicalIntern
equipment:
head: ClothingHeadHatBeretMedic
eyes: ClothingEyesHudMedical
mask: ClothingMaskSterile
neck: ClothingNeckStethoscope
outerClothing: ClothingOuterCoatLabOpened
gloves: ClothingHandsGlovesLatex

View File

@@ -25,3 +25,14 @@
storage: storage:
back: back:
- EmergencyRollerBedSpawnFolded - EmergencyRollerBedSpawnFolded
- type: chameleonOutfit
id: ParamedicChameleonOutfit
job: Paramedic
equipment:
head: ClothingHeadHatParamedicsoft
eyes: ClothingEyesHudMedical
mask: ClothingMaskSterile
neck: ClothingNeckStethoscope
outerClothing: ClothingOuterCoatParamedicWB
gloves: ClothingHandsGlovesLatex

View File

@@ -26,3 +26,14 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: ResearchAssistantChameleonOutfit
job: ResearchAssistant
equipment:
head: ClothingHeadHatCardborg
eyes: ClothingEyesGlasses
mask: ClothingMaskGas
neck: ClothingNeckScarfStripedPurple
outerClothing: ClothingOuterCoatLab
gloves: ClothingHandsGlovesLatex

View File

@@ -37,3 +37,15 @@
storage: storage:
back: back:
- Flash - Flash
- type: chameleonOutfit
id: ResearchDirectorChameleonOutfit
job: ResearchDirector
hasMindShield: true
equipment:
head: ClothingHeadHatBeretRND
eyes: ClothingEyesGlasses
mask: ClothingMaskGas
neck: ClothingNeckCloakRd
outerClothing: ClothingOuterCoatRD
gloves: ClothingHandsGlovesColorPurple

View File

@@ -21,3 +21,14 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: ScientistChameleonOutfit
job: Scientist
equipment:
head: ClothingHeadBandSkull
eyes: ClothingEyesGlassesCheapSunglasses
mask: ClothingMaskGas
neck: ClothingNeckTieSci
outerClothing: ClothingOuterCoatRnd
gloves: ClothingHandsGlovesLatex

View File

@@ -35,3 +35,13 @@
- Flash - Flash
- ForensicPad - ForensicPad
- ForensicScanner - ForensicScanner
- type: chameleonOutfit
id: DetectiveChameleonOutfit
job: Detective
hasMindShield: true
equipment:
head: ClothingHeadHatFedoraBrown
mask: ClothingMaskGasSecurity
neck: ClothingNeckTieDet
gloves: ClothingHandsGlovesForensic

View File

@@ -51,3 +51,11 @@
back: back:
- Flash - Flash
- MagazinePistol - MagazinePistol
- type: chameleonOutfit
id: HeadOfSecurityChameleonOutfit
job: HeadOfSecurity
hasMindShield: true
equipment:
mask: ClothingMaskGasSecurity
neck: ClothingNeckCloakHos

View File

@@ -39,3 +39,15 @@
back: back:
- Flash - Flash
- MagazinePistol - MagazinePistol
- type: chameleonOutfit
id: SecurityCadetChameleonOutfit
job: SecurityCadet
hasMindShield: true
equipment:
head: ClothingHeadHelmetBasic
eyes: ClothingEyesHudSecurity
mask: ClothingMaskGasSecurity
neck: ClothingNeckScarfStripedRed
belt: ClothingBeltSecurity
gloves: ClothingHandsGlovesColorBlack

View File

@@ -32,3 +32,13 @@
back: back:
- Flash - Flash
- MagazinePistol - MagazinePistol
- type: chameleonOutfit
id: SecurityOfficerChameleonOutfit
job: SecurityOfficer
hasMindShield: true
equipment:
head: ClothingHeadHatBeretSecurity
mask: ClothingMaskGasSecurity
neck: Dinkystar
gloves: ClothingHandsGlovesColorBlack

View File

@@ -39,3 +39,17 @@
back: back:
- Flash - Flash
- MagazinePistol - 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

View File

@@ -24,3 +24,14 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: BoxerChameleonOutfit
job: Boxer
equipment:
head: ClothingHeadHatBlacksoft
eyes: ClothingEyesGlassesCheapSunglasses
mask: ClothingMaskBreath
outerClothing: ClothingOuterWinterColorGray
jumpsuit: UniformShortsRedWithTop
neck: ClothingNeckScarfStripedRed

View File

@@ -21,3 +21,15 @@
storage: storage:
back: back:
- RubberStampPsychologist - RubberStampPsychologist
- type: chameleonOutfit
id: PsychologistChameleonOutfit
job: Psychologist
equipment:
head: ClothingHeadHatBeretMedic
eyes: ClothingEyesHudMedical
mask: ClothingMaskSterile
outerClothing: ClothingOuterCoatLab
neck: ClothingNeckStethoscope
belt: ClothingBeltMedical
gloves: ClothingHandsGlovesLatex

View File

@@ -19,3 +19,14 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: ReporterChameleonOutfit
job: Reporter
equipment:
head: ClothingHeadHatFedoraGrey
eyes: ClothingEyesGlassesCheapSunglasses
mask: ClothingMaskBreath
outerClothing: ClothingOuterCoatTrench
neck: ClothingNeckTieRed
gloves: ClothingHandsGlovesFingerless

View File

@@ -25,3 +25,15 @@
#storage: #storage:
#back: #back:
#- Stuff #- Stuff
- type: chameleonOutfit
id: ZookeeperChameleonOutfit
job: Zookeeper
equipment:
eyes: ClothingEyesGlassesCheapSunglasses
mask: ClothingMaskBreath
outerClothing: ClothingOuterWinterColorLightBrown
neck: ClothingNeckScarfStripedBrown
belt: ClothingBeltStorageWaistbag
gloves: ClothingHandsGlovesColorBrown

View File

@@ -932,6 +932,9 @@
- type: Tag - type: Tag
id: MimeHappyHonk id: MimeHappyHonk
- type: Tag
id: FakeMindShieldImplant
- type: Tag - type: Tag
id: MindTransferTarget id: MindTransferTarget

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

View File

@@ -1,7 +1,7 @@
{ {
"version": 1, "version": 1,
"license": "CC-BY-SA-3.0", "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": { "size": {
"x": 32, "x": 32,
"y": 32 "y": 32
@@ -12,6 +12,9 @@
}, },
{ {
"name": "explosive" "name": "explosive"
},
{
"name": "chameleon"
} }
] ]
} }