diff --git a/Content.Client/Clothing/ClothingComponent.cs b/Content.Client/Clothing/ClothingComponent.cs index 28dfcf38a2..c03ea58ac1 100644 --- a/Content.Client/Clothing/ClothingComponent.cs +++ b/Content.Client/Clothing/ClothingComponent.cs @@ -7,17 +7,6 @@ namespace Content.Client.Clothing [ComponentReference(typeof(SharedClothingComponent))] public sealed class ClothingComponent : SharedClothingComponent { - [ViewVariables(VVAccess.ReadWrite)] - [DataField("femaleMask")] - public FemaleClothingMask FemaleMask = FemaleClothingMask.UniformFull; - public string? InSlot; } - - public enum FemaleClothingMask : byte - { - NoMask = 0, - UniformFull, - UniformTop - } } diff --git a/Content.Client/Clothing/Systems/ChameleonClothingSystem.cs b/Content.Client/Clothing/Systems/ChameleonClothingSystem.cs new file mode 100644 index 0000000000..a0142614c7 --- /dev/null +++ b/Content.Client/Clothing/Systems/ChameleonClothingSystem.cs @@ -0,0 +1,110 @@ +using System.Linq; +using Content.Shared.Clothing.Components; +using Content.Shared.Clothing.EntitySystems; +using Content.Shared.Inventory; +using Robust.Client.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Client.Clothing.Systems; + +// All valid items for chameleon are calculated on client startup and stored in dictionary. +public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IComponentFactory _factory = default!; + + private static readonly SlotFlags[] IgnoredSlots = + { + SlotFlags.All, + SlotFlags.PREVENTEQUIP, + SlotFlags.NONE + }; + private static readonly SlotFlags[] Slots = Enum.GetValues().Except(IgnoredSlots).ToArray(); + + private readonly Dictionary> _data = new(); + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(HandleState); + + PrepareAllVariants(); + _proto.PrototypesReloaded += OnProtoReloaded; + } + + public override void Shutdown() + { + base.Shutdown(); + _proto.PrototypesReloaded -= OnProtoReloaded; + } + + private void OnProtoReloaded(PrototypesReloadedEventArgs _) + { + PrepareAllVariants(); + } + + private void HandleState(EntityUid uid, ChameleonClothingComponent component, ref ComponentHandleState args) + { + if (args.Current is not ChameleonClothingComponentState state) + return; + component.SelectedId = state.SelectedId; + + UpdateVisuals(uid, component); + } + + protected override void UpdateSprite(EntityUid uid, EntityPrototype proto) + { + base.UpdateSprite(uid, proto); + if (TryComp(uid, out SpriteComponent? sprite) + && proto.TryGetComponent(out SpriteComponent? otherSprite, _factory)) + { + sprite.CopyFrom(otherSprite); + } + } + + /// + /// Get a list of valid chameleon targets for these slots. + /// + public IEnumerable GetValidTargets(SlotFlags slot) + { + var set = new HashSet(); + foreach (var availableSlot in _data.Keys) + { + if (slot.HasFlag(availableSlot)) + { + set.UnionWith(_data[availableSlot]); + } + } + return set; + } + + private void PrepareAllVariants() + { + _data.Clear(); + var prototypes = _proto.EnumeratePrototypes(); + + foreach (var proto in prototypes) + { + // check if this is valid clothing + if (!IsValidTarget(proto)) + continue; + if (!proto.TryGetComponent(out ClothingComponent? item, _factory)) + continue; + + // sort item by their slot flags + // one item can be placed in several buckets + foreach (var slot in Slots) + { + if (!item.Slots.HasFlag(slot)) + continue; + + if (!_data.ContainsKey(slot)) + { + _data.Add(slot, new List()); + } + _data[slot].Add(proto.ID); + } + } + } +} diff --git a/Content.Client/Clothing/UI/ChameleonBoundUserInterface.cs b/Content.Client/Clothing/UI/ChameleonBoundUserInterface.cs new file mode 100644 index 0000000000..7ea6c6b90f --- /dev/null +++ b/Content.Client/Clothing/UI/ChameleonBoundUserInterface.cs @@ -0,0 +1,57 @@ +using Content.Client.Clothing.Systems; +using Content.Shared.Clothing.Components; +using JetBrains.Annotations; +using Robust.Client.GameObjects; + +namespace Content.Client.Clothing.UI; + +[UsedImplicitly] +public sealed class ChameleonBoundUserInterface : BoundUserInterface +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + private readonly ChameleonClothingSystem _chameleon; + + private ChameleonMenu? _menu; + + public ChameleonBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + _chameleon = _entityManager.System(); + } + + protected override void Open() + { + base.Open(); + + _menu = new ChameleonMenu(); + _menu.OnClose += Close; + _menu.OnIdSelected += OnIdSelected; + _menu.OpenCentered(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + if (state is not ChameleonBoundUserInterfaceState st) + return; + + var targets = _chameleon.GetValidTargets(st.Slot); + _menu?.UpdateState(targets, st.SelectedId); + } + + private void OnIdSelected(string selectedId) + { + SendMessage(new ChameleonPrototypeSelectedMessage(selectedId)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _menu?.Close(); + _menu = null; + } + } +} diff --git a/Content.Client/Clothing/UI/ChameleonMenu.xaml b/Content.Client/Clothing/UI/ChameleonMenu.xaml new file mode 100644 index 0000000000..2873187354 --- /dev/null +++ b/Content.Client/Clothing/UI/ChameleonMenu.xaml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/Content.Client/Clothing/UI/ChameleonMenu.xaml.cs b/Content.Client/Clothing/UI/ChameleonMenu.xaml.cs new file mode 100644 index 0000000000..fc0e7bbb80 --- /dev/null +++ b/Content.Client/Clothing/UI/ChameleonMenu.xaml.cs @@ -0,0 +1,90 @@ +using System.Linq; +using Content.Client.Clothing.Systems; +using Content.Client.Stylesheets; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; + +namespace Content.Client.Clothing.UI; + +[GenerateTypedNameReferences] +public sealed partial class ChameleonMenu : DefaultWindow +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + private readonly SpriteSystem _sprite; + public event Action? OnIdSelected; + + private IEnumerable _possibleIds = Enumerable.Empty(); + private string? _selectedId; + private string _searchFilter = ""; + + public ChameleonMenu() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + _sprite = _entityManager.System(); + + Search.OnTextChanged += OnSearchEntered; + } + + public void UpdateState(IEnumerable possibleIds, string? selectedId) + { + _possibleIds = possibleIds; + _selectedId = selectedId; + UpdateGrid(); + } + + private void OnSearchEntered(LineEdit.LineEditEventArgs obj) + { + _searchFilter = obj.Text; + UpdateGrid(); + } + + private void UpdateGrid() + { + ClearGrid(); + + var group = new ButtonGroup(); + var searchFilterLow = _searchFilter.ToLowerInvariant(); + + foreach (var id in _possibleIds) + { + if (!_prototypeManager.TryIndex(id, out EntityPrototype? proto)) + continue; + + var lowId = id.ToLowerInvariant(); + var lowName = proto.Name.ToLowerInvariant(); + if (!lowId.Contains(searchFilterLow) && !lowName.Contains(_searchFilter)) + continue; + + var button = new Button + { + MinSize = new Vector2(48, 48), + HorizontalExpand = true, + Group = group, + StyleClasses = {StyleBase.ButtonSquare}, + ToggleMode = true, + Pressed = _selectedId == id, + ToolTip = proto.Name + }; + button.OnPressed += _ => OnIdSelected?.Invoke(id); + Grid.AddChild(button); + + var texture = _sprite.GetPrototypeIcon(proto); + button.AddChild(new TextureRect + { + Stretch = TextureRect.StretchMode.KeepAspectCentered, + Texture = texture.Default + }); + } + } + + private void ClearGrid() + { + Grid.RemoveAllChildren(); + } +} diff --git a/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs b/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs new file mode 100644 index 0000000000..862f41dd0f --- /dev/null +++ b/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs @@ -0,0 +1,98 @@ +using Content.Shared.Clothing.Components; +using Content.Shared.Clothing.EntitySystems; +using Content.Shared.Verbs; +using Robust.Server.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Server.Clothing.Systems; + +public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(GetState); + SubscribeLocalEvent>(OnVerb); + SubscribeLocalEvent(OnSelected); + } + + private void OnInit(EntityUid uid, ChameleonClothingComponent component, ComponentInit args) + { + SetSelectedPrototype(uid, component.SelectedId, true, component); + } + + private void GetState(EntityUid uid, ChameleonClothingComponent component, ref ComponentGetState args) + { + args.State = new ChameleonClothingComponentState + { + SelectedId = component.SelectedId + }; + } + + private void OnVerb(EntityUid uid, ChameleonClothingComponent component, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + args.Verbs.Add(new InteractionVerb() + { + Text = Loc.GetString("chameleon-component-verb-text"), + IconTexture = "/Textures/Interface/VerbIcons/settings.svg.192dpi.png", + Act = () => TryOpenUi(uid, args.User, component) + }); + } + + private void OnSelected(EntityUid uid, ChameleonClothingComponent component, ChameleonPrototypeSelectedMessage args) + { + SetSelectedPrototype(uid, args.SelectedId, component: component); + } + + private void TryOpenUi(EntityUid uid, EntityUid user, ChameleonClothingComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + if (!TryComp(user, out ActorComponent? actor)) + return; + _uiSystem.TryToggleUi(uid, ChameleonUiKey.Key, actor.PlayerSession); + } + + private void UpdateUi(EntityUid uid, ChameleonClothingComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + var state = new ChameleonBoundUserInterfaceState(component.Slot, component.SelectedId); + _uiSystem.TrySetUiState(uid, ChameleonUiKey.Key, state); + } + + /// + /// Change chameleon items name, description and sprite to mimic other entity prototype. + /// + public void SetSelectedPrototype(EntityUid uid, string? protoId, bool forceUpdate = false, + ChameleonClothingComponent? component = null) + { + if (!Resolve(uid, ref component, false)) + return; + + // check that wasn't already selected + // forceUpdate on component init ignores this check + if (component.SelectedId == protoId && !forceUpdate) + return; + + // make sure that it is valid change + if (string.IsNullOrEmpty(protoId) || !_proto.TryIndex(protoId, out EntityPrototype? proto)) + return; + if (!IsValidTarget(proto, component.Slot)) + return; + component.SelectedId = protoId; + + UpdateVisuals(uid, component); + UpdateUi(uid, component); + Dirty(component); + } +} diff --git a/Content.Shared/Clothing/Components/ChameleonClothingComponent.cs b/Content.Shared/Clothing/Components/ChameleonClothingComponent.cs new file mode 100644 index 0000000000..567a23a8f5 --- /dev/null +++ b/Content.Shared/Clothing/Components/ChameleonClothingComponent.cs @@ -0,0 +1,66 @@ +using Content.Shared.Clothing.EntitySystems; +using Content.Shared.Inventory; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Clothing.Components; + +/// +/// Allow players to change clothing sprite to any other clothing prototype. +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedChameleonClothingSystem))] +public sealed class ChameleonClothingComponent : Component +{ + /// + /// Filter possible chameleon options by their slot flag. + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("slot", required: true)] + public SlotFlags Slot; + + /// + /// EntityPrototype id that chameleon item is trying to mimic. + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("default", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? SelectedId; +} + +[Serializable, NetSerializable] +public sealed class ChameleonClothingComponentState : ComponentState +{ + public string? SelectedId; +} + +[Serializable, NetSerializable] +public sealed class ChameleonBoundUserInterfaceState : BoundUserInterfaceState +{ + public readonly SlotFlags Slot; + public readonly string? SelectedId; + + public ChameleonBoundUserInterfaceState(SlotFlags slot, string? selectedId) + { + Slot = slot; + SelectedId = selectedId; + } +} + +[Serializable, NetSerializable] +public sealed class ChameleonPrototypeSelectedMessage: BoundUserInterfaceMessage +{ + public readonly string SelectedId; + + public ChameleonPrototypeSelectedMessage(string selectedId) + { + SelectedId = selectedId; + } +} + +[Serializable, NetSerializable] +public enum ChameleonUiKey : byte +{ + Key +} diff --git a/Content.Shared/Clothing/Components/SharedClothingComponent.cs b/Content.Shared/Clothing/Components/SharedClothingComponent.cs index f5cb9f8b86..0f69042b81 100644 --- a/Content.Shared/Clothing/Components/SharedClothingComponent.cs +++ b/Content.Shared/Clothing/Components/SharedClothingComponent.cs @@ -42,6 +42,10 @@ public abstract class SharedClothingComponent : Component [ViewVariables(VVAccess.ReadWrite)] [DataField("sprite")] public string? RsiPath; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("femaleMask")] + public FemaleClothingMask FemaleMask = FemaleClothingMask.UniformFull; } [Serializable, NetSerializable] @@ -54,3 +58,10 @@ public sealed class ClothingComponentState : ComponentState EquippedPrefix = equippedPrefix; } } + +public enum FemaleClothingMask : byte +{ + NoMask = 0, + UniformFull, + UniformTop +} diff --git a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs index 10336d2993..a08040a672 100644 --- a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs @@ -52,5 +52,22 @@ public sealed class ClothingSystem : EntitySystem Dirty(clothing); } + /// + /// Copy all clothing specific visuals from another item. + /// + public void CopyVisuals(EntityUid uid, SharedClothingComponent otherClothing, SharedClothingComponent? clothing = null) + { + if (!Resolve(uid, ref clothing)) + return; + + clothing.ClothingVisuals = otherClothing.ClothingVisuals; + clothing.EquippedPrefix = otherClothing.EquippedPrefix; + clothing.RsiPath = otherClothing.RsiPath; + clothing.FemaleMask = otherClothing.FemaleMask; + + _itemSys.VisualsChanged(uid); + Dirty(clothing); + } + #endregion } diff --git a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs new file mode 100644 index 0000000000..1f5f96f73c --- /dev/null +++ b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs @@ -0,0 +1,72 @@ +using Content.Shared.Clothing.Components; +using Content.Shared.Inventory; +using Content.Shared.Item; +using Content.Shared.Tag; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Clothing.EntitySystems; + +public abstract class SharedChameleonClothingSystem : EntitySystem +{ + [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly SharedItemSystem _itemSystem = default!; + [Dependency] private readonly ClothingSystem _clothingSystem = default!; + + // Updates chameleon visuals and meta information. + // This function is called on a server after user selected new outfit. + // And after that on a client after state was updated. + // This 100% makes sure that server and client have exactly same data. + protected void UpdateVisuals(EntityUid uid, ChameleonClothingComponent component) + { + if (string.IsNullOrEmpty(component.SelectedId) || + !_proto.TryIndex(component.SelectedId, out EntityPrototype? proto)) + return; + + // world sprite icon + UpdateSprite(uid, proto); + + // copy name and description + var meta = MetaData(uid); + meta.EntityName = proto.Name; + meta.EntityDescription = proto.Description; + + // item sprite logic + if (TryComp(uid, out ItemComponent? item) && + proto.TryGetComponent(out ItemComponent? otherItem, _factory)) + { + _itemSystem.CopyVisuals(uid, otherItem, item); + } + + // clothing sprite logic + if (TryComp(uid, out SharedClothingComponent? clothing) && + proto.TryGetComponent("Clothing", out SharedClothingComponent? otherClothing)) + { + _clothingSystem.CopyVisuals(uid, otherClothing, clothing); + } + } + + protected virtual void UpdateSprite(EntityUid uid, EntityPrototype proto) { } + + /// + /// Check if this entity prototype is valid target for chameleon item. + /// + public bool IsValidTarget(EntityPrototype proto, SlotFlags chameleonSlot = SlotFlags.NONE) + { + // check if entity is valid + if (proto.Abstract || proto.NoSpawn) + return false; + + // check if it is marked as valid chameleon target + if (!proto.TryGetComponent(out TagComponent? tags, _factory) || !tags.Tags.Contains("WhitelistChameleon")) + return false; + + // check if it's valid clothing + if (!proto.TryGetComponent("Clothing", out SharedClothingComponent? clothing)) + return false; + if (!clothing.Slots.HasFlag(chameleonSlot)) + return false; + + return true; + } +} diff --git a/Content.Shared/Item/ItemComponent.cs b/Content.Shared/Item/ItemComponent.cs index d53a81104f..7540901d88 100644 --- a/Content.Shared/Item/ItemComponent.cs +++ b/Content.Shared/Item/ItemComponent.cs @@ -18,6 +18,7 @@ public sealed class ItemComponent : Component [Access(typeof(SharedItemSystem), Other = AccessPermissions.ReadExecute)] public int Size = 5; + [Access(typeof(SharedItemSystem))] [DataField("inhandVisuals")] public Dictionary> InhandVisuals = new(); @@ -29,9 +30,10 @@ public sealed class ItemComponent : Component /// /// Rsi of the sprite shown on the player when this item is in their hands. Used to generate a default entry for /// + [Access(typeof(SharedItemSystem))] [ViewVariables(VVAccess.ReadWrite)] [DataField("sprite")] - public readonly string? RsiPath; + public string? RsiPath; } [Serializable, NetSerializable] diff --git a/Content.Shared/Item/SharedItemSystem.cs b/Content.Shared/Item/SharedItemSystem.cs index a00d594d7a..8365af4594 100644 --- a/Content.Shared/Item/SharedItemSystem.cs +++ b/Content.Shared/Item/SharedItemSystem.cs @@ -49,6 +49,22 @@ public abstract class SharedItemSystem : EntitySystem VisualsChanged(uid); } + /// + /// Copy all item specific visuals from another item. + /// + public void CopyVisuals(EntityUid uid, ItemComponent otherItem, ItemComponent? item = null) + { + if (!Resolve(uid, ref item)) + return; + + item.RsiPath = otherItem.RsiPath; + item.InhandVisuals = otherItem.InhandVisuals; + item.HeldPrefix = otherItem.HeldPrefix; + + Dirty(item); + VisualsChanged(uid); + } + #endregion private void OnHandInteract(EntityUid uid, ItemComponent component, InteractHandEvent args) diff --git a/Resources/Locale/en-US/clothing/components/chameleon-component.ftl b/Resources/Locale/en-US/clothing/components/chameleon-component.ftl new file mode 100644 index 0000000000..19ab3834d9 --- /dev/null +++ b/Resources/Locale/en-US/clothing/components/chameleon-component.ftl @@ -0,0 +1,7 @@ + +## UI +chameleon-component-ui-window-name = Chameleon Settings +chameleon-component-ui-search-placeholder = Search... + +## Verb +chameleon-component-verb-text = Chameleon diff --git a/Resources/Locale/en-US/forensics/fibers.ftl b/Resources/Locale/en-US/forensics/fibers.ftl index 4d420be823..f1117c662c 100644 --- a/Resources/Locale/en-US/forensics/fibers.ftl +++ b/Resources/Locale/en-US/forensics/fibers.ftl @@ -8,6 +8,7 @@ fibers-durathread = durathread fibers-latex = latex fibers-nitrile = nitrile fibers-nanomachines = insulative nanomachine +fibers-chameleon = holographic chameleon fibers-purple = purple fibers-red = red diff --git a/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml b/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml index a280d51072..9b3cd820e0 100644 --- a/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml +++ b/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml @@ -30,7 +30,7 @@ amount: 5 - type: entity - parent: ClothingBackpackDuffelSyndicateMedical + parent: ClothingBackpackDuffelSyndicateMedicalBundle id: ClothingBackpackDuffelSyndicateFilledMedical name: syndicate surgical duffel bag description: "A large duffel bag for holding extra medical supplies - this one seems to be designed for holding surgical tools." @@ -45,7 +45,7 @@ - id: ScalpelAdvanced - type: entity - parent: ClothingBackpackDuffelSyndicateAmmo + parent: ClothingBackpackDuffelSyndicateAmmoBundle id: ClothingBackpackDuffelSyndicateFilledShotgun name: Bulldog bundle description: "Lean and mean: Contains the popular Bulldog Shotgun, a 12g beanbag drum and 2 12g buckshot drums." #, and a pair of Thermal Imaging Goggles. @@ -58,7 +58,7 @@ # - id: ThermalImagingGoggles - type: entity - parent: ClothingBackpackDuffelSyndicateAmmo + parent: ClothingBackpackDuffelSyndicateAmmoBundle id: ClothingBackpackDuffelSyndicateFilledSMG name: C-20r bundle description: "Old faithful: The classic C-20r Submachine Gun, bundled with three magazines." #, and a Suppressor. @@ -71,7 +71,7 @@ # - id: SMGSuppressor - type: entity - parent: ClothingBackpackDuffelSyndicateAmmo + parent: ClothingBackpackDuffelSyndicateAmmoBundle id: ClothingBackpackDuffelSyndicateFilledRevolver name: Python bundle description: "Go loud and proud with a fully loaded Magnum Python, bundled with two speed loaders." @@ -83,7 +83,7 @@ amount: 2 - type: entity - parent: ClothingBackpackDuffelSyndicateAmmo + parent: ClothingBackpackDuffelSyndicateAmmoBundle id: ClothingBackpackDuffelSyndicateFilledLMG name: L6 Saw bundle description: "More dakka: The iconic L6 lightmachinegun, bundled with 2 box magazines." @@ -94,7 +94,7 @@ - id: MagazineLightRifleBox - type: entity - parent: ClothingBackpackDuffelSyndicateAmmo + parent: ClothingBackpackDuffelSyndicateAmmoBundle id: ClothingBackpackDuffelSyndicateFilledGrenadeLauncher name: China-Lake bundle description: "An old China-Lake grenade launcher bundled with 8 rounds of various destruction capability." @@ -108,7 +108,7 @@ amount: 4 - type: entity - parent: ClothingBackpackDuffelSyndicateAmmo + parent: ClothingBackpackDuffelSyndicateAmmoBundle id: ClothingBackpackDuffelSyndicateFilledCarbine name: M-90gl bundle description: "A versatile battle rifle with an attached grenade launcher, bundled with 3 magazines and 6 grenades of various capabilities." @@ -131,6 +131,8 @@ name: CentCom official costume duffel bag description: "Contains a full CentCom Official uniform set, headset and clipboard included. The headset comes without an encryption key." components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag - type: StorageFill contents: - id: ClothingHeadHatCaptain @@ -150,6 +152,8 @@ name: clown costume duffel bag description: "Contains a complete Clown outfit." components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag - type: StorageFill contents: - id: ClothingUniformJumpsuitClown @@ -160,7 +164,7 @@ - id: ClothingHeadsetService - type: entity - parent: ClothingBackpackDuffelSyndicate + parent: ClothingBackpackDuffelSyndicateBundle id: ClothingBackpackDuffelSyndicatePyjamaBundle name: syndicate pyjama duffel bag description: "Contains 3 syndicate pyjamas." @@ -182,7 +186,7 @@ - id: PlushieLizard - type: entity - parent: ClothingBackpackDuffelSyndicate + parent: ClothingBackpackDuffelSyndicateBundle id: ClothingBackpackDuffelSyndicateC4tBundle name: syndicate C-4 bundle description: "Contains a lot of C-4 charges." @@ -193,7 +197,24 @@ amount: 8 - type: entity - parent: ClothingBackpackDuffelSyndicate + parent: ClothingBackpackChameleon + id: ClothingBackpackChameleonFill + suffix: Fill, Chameleon + components: + - type: StorageFill + contents: + - id: ClothingUniformJumpsuitChameleon + - id: ClothingOuterChameleon + - id: ClothingNeckChameleon + - id: ClothingMaskGasChameleon + - id: ClothingHeadHatChameleon + - id: ClothingHandsChameleon + - id: ClothingEyesChameleon + - id: ClothingHeadsetChameleon + - id: ClothingShoesChameleon + +- type: entity + parent: ClothingBackpackDuffelSyndicateBundle id: ClothingBackpackDuffelSyndicateEVABundle name: syndicate EVA bundle description: "Contains the Syndicate approved EVA suit." @@ -204,7 +225,7 @@ - id: ClothingOuterHardsuitSyndicate - type: entity - parent: ClothingBackpackDuffelSyndicate + parent: ClothingBackpackDuffelSyndicateBundle id: ClothingBackpackDuffelSyndicateHardsuitBundle name: syndicate hardsuit bundle description: "Contains the Syndicate's signature blood red hardsuit." @@ -214,7 +235,7 @@ - id: ClothingOuterHardsuitSyndie - type: entity - parent: ClothingBackpackDuffelSyndicate + parent: ClothingBackpackDuffelSyndicateBundle id: ClothingBackpackDuffelZombieBundle name: syndicate zombie bundle description: "An all-in-one kit for unleashing the undead upon a station." @@ -228,7 +249,7 @@ amount: 3 - type: entity - parent: ClothingBackpackDuffelSyndicate + parent: ClothingBackpackDuffelSyndicateBundle id: ClothingBackpackDuffelSyndicateOperative name: operative duffelbag components: @@ -241,7 +262,7 @@ - type: entity - parent: ClothingBackpackDuffelSyndicateMedical + parent: ClothingBackpackDuffelSyndicateMedicalBundle id: ClothingBackpackDuffelSyndicateOperativeMedic name: operative medic duffelbag description: A large duffel bag for holding extra medical supplies. diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index 69a700a009..5233e6c172 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -428,7 +428,17 @@ # Armor -# Should be cameleon shoes, change when implemented. +- type: listing + id: UplinkChameleon + name: Chameleon Kit + description: A backpack full of items that contain chameleon technology allowing you to disguise as pretty much anything on the station, and more! + productEntity: ClothingBackpackChameleonFill + icon: /Textures/Clothing/Uniforms/Jumpsuit/rainbow.rsi/icon.png + cost: + Telecrystal: 4 + categories: + - UplinkArmor + - type: listing id: UplinkClothingNoSlipsShoes name: no-slip shoes diff --git a/Resources/Prototypes/Entities/Clothing/Back/duffel.yml b/Resources/Prototypes/Entities/Clothing/Back/duffel.yml index 8b2d360306..9bd79448df 100644 --- a/Resources/Prototypes/Entities/Clothing/Back/duffel.yml +++ b/Resources/Prototypes/Entities/Clothing/Back/duffel.yml @@ -117,6 +117,14 @@ - type: Storage capacity: 131 +- type: entity + parent: ClothingBackpackDuffelSyndicate + id: ClothingBackpackDuffelSyndicateBundle + abstract: true + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: entity parent: ClothingBackpackDuffelSyndicate id: ClothingBackpackDuffelSyndicateAmmo @@ -130,6 +138,14 @@ - type: Clothing equippedPrefix: ammo +- type: entity + parent: ClothingBackpackDuffelSyndicate + id: ClothingBackpackDuffelSyndicateAmmoBundle + abstract: true + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: entity parent: ClothingBackpackDuffelSyndicate id: ClothingBackpackDuffelSyndicateMedical @@ -143,6 +159,14 @@ - type: Clothing equippedPrefix: med +- type: entity + parent: ClothingBackpackDuffelSyndicate + id: ClothingBackpackDuffelSyndicateMedicalBundle + abstract: true + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: entity parent: ClothingBackpackDuffel id: ClothingBackpackDuffelHolding diff --git a/Resources/Prototypes/Entities/Clothing/Back/specific.yml b/Resources/Prototypes/Entities/Clothing/Back/specific.yml new file mode 100644 index 0000000000..d726fa5cac --- /dev/null +++ b/Resources/Prototypes/Entities/Clothing/Back/specific.yml @@ -0,0 +1,23 @@ +- type: entity + parent: ClothingBackpack + id: ClothingBackpackChameleon + name: backpack + description: You wear this on your back and put items into it. + suffix: Chameleon + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: Sprite + sprite: Clothing/Back/Backpacks/backpack.rsi + netsync: false + - type: Clothing + sprite: Clothing/Back/Backpacks/backpack.rsi + - type: ChameleonClothing + slot: [back] + default: ClothingBackpack + - type: UserInterface + interfaces: + - key: enum.StorageUiKey.Key + type: StorageBoundUserInterface + - key: enum.ChameleonUiKey.Key + type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/Ears/specific.yml b/Resources/Prototypes/Entities/Clothing/Ears/specific.yml new file mode 100644 index 0000000000..fcdca8d0a7 --- /dev/null +++ b/Resources/Prototypes/Entities/Clothing/Ears/specific.yml @@ -0,0 +1,21 @@ +- type: entity + parent: ClothingHeadsetGrey + id: ClothingHeadsetChameleon + name: passenger headset + description: An updated, modular intercom that fits over the head. Takes encryption keys. + suffix: Chameleon + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: Sprite + sprite: Clothing/Ears/Headsets/base.rsi + netsync: false + - type: Clothing + sprite: Clothing/Ears/Headsets/base.rsi + - type: ChameleonClothing + slot: [ears] + default: ClothingHeadsetGrey + - type: UserInterface + interfaces: + - key: enum.ChameleonUiKey.Key + type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml b/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml new file mode 100644 index 0000000000..a4d3fc5c3f --- /dev/null +++ b/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml @@ -0,0 +1,21 @@ +- type: entity + parent: ClothingEyesBase + id: ClothingEyesChameleon # no flash immunity, sorry + name: sun glasses + description: Useful both for security and cargonia. + suffix: Chameleon + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: Sprite + sprite: Clothing/Eyes/Glasses/sunglasses.rsi + netsync: false + - type: Clothing + sprite: Clothing/Eyes/Glasses/sunglasses.rsi + - type: ChameleonClothing + slot: [eyes] + default: ClothingEyesGlassesSunglasses + - type: UserInterface + interfaces: + - key: enum.ChameleonUiKey.Key + type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml index 5a1913ce6e..9a031534d0 100644 --- a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml +++ b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml @@ -242,6 +242,8 @@ name: black gloves #Intentionally named after regular gloves, they're meant to be sneaky. description: Seemingly regular black gloves. The fingertips are outfitted with nanotech that makes stealing a breeze. components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag - type: Sprite sprite: Clothing/Hands/Gloves/Color/black.rsi - type: Clothing diff --git a/Resources/Prototypes/Entities/Clothing/Hands/specific.yml b/Resources/Prototypes/Entities/Clothing/Hands/specific.yml new file mode 100644 index 0000000000..25d8300012 --- /dev/null +++ b/Resources/Prototypes/Entities/Clothing/Hands/specific.yml @@ -0,0 +1,24 @@ +- type: entity + parent: ClothingHandsBase + id: ClothingHandsChameleon # doesn't protect from electricity or heat + name: black gloves + description: Regular black gloves that do not keep you from frying. + suffix: Chameleon + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: Sprite + sprite: Clothing/Hands/Gloves/Color/black.rsi + netsync: false + - type: Clothing + sprite: Clothing/Hands/Gloves/Color/black.rsi + - type: ChameleonClothing + slot: [gloves] + default: ClothingHandsGlovesColorBlack + - type: Fiber + fiberMaterial: fibers-chameleon + - type: FingerprintMask + - type: UserInterface + interfaces: + - key: enum.ChameleonUiKey.Key + type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml index a8055f0097..03f49baeba 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml @@ -11,6 +11,7 @@ - type: Tag tags: - DroneUsable + - WhitelistChameleon - type: entity abstract: true @@ -22,6 +23,9 @@ - HEAD - type: Sprite state: icon + - type: Tag + tags: + - WhitelistChameleon - type: entity abstract: true @@ -78,6 +82,7 @@ - type: Tag tags: - HidesHair + - WhitelistChameleon - type: DiseaseProtection protection: 0.05 - type: IdentityBlocker @@ -116,6 +121,7 @@ - type: Tag tags: - HidesHair + - WhitelistChameleon - type: DiseaseProtection protection: 0.05 - type: IdentityBlocker diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml b/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml index ac04b37299..20bf63df30 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml @@ -37,6 +37,8 @@ - type: PowerCellSlot cellSlot: startingItem: PowerCellMedium + - type: Item + heldPrefix: off - type: ContainerContainer containers: cell_slot: !type:ContainerSlot diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml index e35486c745..8328d8c3ff 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml @@ -255,6 +255,7 @@ - type: Tag tags: - HidesHair + - WhitelistChameleon - type: entity parent: ClothingHeadBase diff --git a/Resources/Prototypes/Entities/Clothing/Head/specific.yml b/Resources/Prototypes/Entities/Clothing/Head/specific.yml new file mode 100644 index 0000000000..bba65c6613 --- /dev/null +++ b/Resources/Prototypes/Entities/Clothing/Head/specific.yml @@ -0,0 +1,21 @@ +- type: entity + parent: ClothingHeadBase + id: ClothingHeadHatChameleon + name: beret + description: A beret, an artists favorite headwear. + suffix: Chameleon + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: Sprite + sprite: Clothing/Head/Hats/beret.rsi + netsync: false + - type: Clothing + sprite: Clothing/Head/Hats/beret.rsi + - type: ChameleonClothing + slot: [HEAD] + default: ClothingHeadHatBeret + - type: UserInterface + interfaces: + - key: enum.ChameleonUiKey.Key + type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/Masks/specific.yml b/Resources/Prototypes/Entities/Clothing/Masks/specific.yml new file mode 100644 index 0000000000..a30f1fcb22 --- /dev/null +++ b/Resources/Prototypes/Entities/Clothing/Masks/specific.yml @@ -0,0 +1,21 @@ +- type: entity + parent: ClothingMaskBase + id: ClothingMaskGasChameleon + name: gas mask + description: A face-covering mask that can be connected to an air supply. + suffix: Chameleon + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: Sprite + sprite: Clothing/Mask/gas.rsi + netsync: false + - type: Clothing + sprite: Clothing/Mask/gas.rsi + - type: ChameleonClothing + slot: [mask] + default: ClothingMaskGas + - type: UserInterface + interfaces: + - key: enum.ChameleonUiKey.Key + type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/Neck/specific.yml b/Resources/Prototypes/Entities/Clothing/Neck/specific.yml new file mode 100644 index 0000000000..b9bfde68e4 --- /dev/null +++ b/Resources/Prototypes/Entities/Clothing/Neck/specific.yml @@ -0,0 +1,21 @@ +- type: entity + parent: ClothingNeckBase + id: ClothingNeckChameleon + name: striped red scarf + description: A stylish striped red scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks. + suffix: Chameleon + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: Sprite + sprite: Clothing/Neck/Scarfs/red.rsi + netsync: false + - type: Clothing + sprite: Clothing/Neck/Scarfs/red.rsi + - type: ChameleonClothing + slot: [neck] + default: ClothingNeckScarfStripedRed + - type: UserInterface + interfaces: + - key: enum.ChameleonUiKey.Key + type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml new file mode 100644 index 0000000000..4977be5626 --- /dev/null +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml @@ -0,0 +1,21 @@ +- type: entity + parent: ClothingOuterBase + id: ClothingOuterChameleon + name: vest + description: A thick vest with a rubbery, water-resistant shell. + suffix: Chameleon + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: Sprite + sprite: Clothing/OuterClothing/Vests/vest.rsi + netsync: false + - type: Clothing + sprite: Clothing/OuterClothing/Vests/vest.rsi + - type: ChameleonClothing + slot: [outerClothing] + default: ClothingOuterVest + - type: UserInterface + interfaces: + - key: enum.ChameleonUiKey.Key + type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml index 98112bbe81..de62b235c4 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml @@ -101,10 +101,32 @@ sprite: Clothing/Shoes/Specific/wizard.rsi - type: entity - parent: ClothingShoesColorBlack + parent: ClothingShoesBase + id: ClothingShoesChameleon + name: black shoes + suffix: Chameleon + description: Stylish black shoes. + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: Sprite + sprite: Clothing/Shoes/Color/black.rsi + netsync: false + - type: Clothing + sprite: Clothing/Shoes/Color/black.rsi + - type: ChameleonClothing + slot: [FEET] + default: ClothingShoesColorBlack + - type: UserInterface + interfaces: + - key: enum.ChameleonUiKey.Key + type: ChameleonBoundUserInterface + +- type: entity + parent: ClothingShoesChameleon id: ClothingShoesChameleonNoSlips name: black shoes #actual name and description in uplink_catalog.yml - suffix: no-slip + suffix: No-slip, Chameleon description: Stylish black shoes. components: - type: NoSlip diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml new file mode 100644 index 0000000000..293379ab48 --- /dev/null +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml @@ -0,0 +1,24 @@ +- type: entity + parent: ClothingUniformBase + id: ClothingUniformJumpsuitChameleon + name: black jumpsuit + description: A generic black jumpsuit with no rank markings. + suffix: Chameleon + components: + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/Color/black.rsi + netsync: false + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/Color/black.rsi + - type: SuitSensor + randomMode: false + mode: SensorOff + - type: ChameleonClothing + slot: [innerclothing] + default: ClothingUniformJumpsuitColorBlack + - type: UserInterface + interfaces: + - key: enum.ChameleonUiKey.Key + type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/base_clothing.yml b/Resources/Prototypes/Entities/Clothing/base_clothing.yml index 60b0545d47..d110a13775 100644 --- a/Resources/Prototypes/Entities/Clothing/base_clothing.yml +++ b/Resources/Prototypes/Entities/Clothing/base_clothing.yml @@ -5,3 +5,6 @@ components: - type: Sprite netsync: false + - type: Tag + tags: + - WhitelistChameleon diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index ea6478bf9d..3b526bf665 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -195,6 +195,9 @@ - type: Tag id: FireAxe +- type: Tag + id: WhitelistChameleon + - type: Tag id: FirelockElectronics