Chameleon clothing (#8444)

Co-authored-by: Moony <moonheart08@users.noreply.github.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
Alex Evgrashin
2022-09-14 10:42:14 +02:00
committed by GitHub
parent 54947c137c
commit 9ce3a18e3f
33 changed files with 868 additions and 29 deletions

View File

@@ -7,17 +7,6 @@ namespace Content.Client.Clothing
[ComponentReference(typeof(SharedClothingComponent))] [ComponentReference(typeof(SharedClothingComponent))]
public sealed class ClothingComponent : SharedClothingComponent public sealed class ClothingComponent : SharedClothingComponent
{ {
[ViewVariables(VVAccess.ReadWrite)]
[DataField("femaleMask")]
public FemaleClothingMask FemaleMask = FemaleClothingMask.UniformFull;
public string? InSlot; public string? InSlot;
} }
public enum FemaleClothingMask : byte
{
NoMask = 0,
UniformFull,
UniformTop
}
} }

View File

@@ -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<SlotFlags>().Except(IgnoredSlots).ToArray();
private readonly Dictionary<SlotFlags, List<string>> _data = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChameleonClothingComponent, ComponentHandleState>(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);
}
}
/// <summary>
/// Get a list of valid chameleon targets for these slots.
/// </summary>
public IEnumerable<string> GetValidTargets(SlotFlags slot)
{
var set = new HashSet<string>();
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<EntityPrototype>();
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<string>());
}
_data[slot].Add(proto.ID);
}
}
}
}

View File

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

View File

@@ -0,0 +1,12 @@
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc 'chameleon-component-ui-window-name'}"
MinSize="250 300"
SetSize="250 300">
<BoxContainer Orientation="Vertical">
<LineEdit Name="Search" PlaceHolder = "{Loc 'chameleon-component-ui-search-placeholder'}"/>
<ScrollContainer VerticalExpand="True">
<GridContainer Name="Grid" Columns="3" Margin="0 5" >
</GridContainer>
</ScrollContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -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<string>? OnIdSelected;
private IEnumerable<string> _possibleIds = Enumerable.Empty<string>();
private string? _selectedId;
private string _searchFilter = "";
public ChameleonMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprite = _entityManager.System<SpriteSystem>();
Search.OnTextChanged += OnSearchEntered;
}
public void UpdateState(IEnumerable<string> 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();
}
}

View File

@@ -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<ChameleonClothingComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<ChameleonClothingComponent, ComponentGetState>(GetState);
SubscribeLocalEvent<ChameleonClothingComponent, GetVerbsEvent<InteractionVerb>>(OnVerb);
SubscribeLocalEvent<ChameleonClothingComponent, ChameleonPrototypeSelectedMessage>(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<InteractionVerb> 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);
}
/// <summary>
/// Change chameleon items name, description and sprite to mimic other entity prototype.
/// </summary>
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);
}
}

View File

@@ -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;
/// <summary>
/// Allow players to change clothing sprite to any other clothing prototype.
/// </summary>
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedChameleonClothingSystem))]
public sealed class ChameleonClothingComponent : Component
{
/// <summary>
/// Filter possible chameleon options by their slot flag.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
[DataField("slot", required: true)]
public SlotFlags Slot;
/// <summary>
/// EntityPrototype id that chameleon item is trying to mimic.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
[DataField("default", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
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
}

View File

@@ -42,6 +42,10 @@ public abstract class SharedClothingComponent : Component
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField("sprite")] [DataField("sprite")]
public string? RsiPath; public string? RsiPath;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("femaleMask")]
public FemaleClothingMask FemaleMask = FemaleClothingMask.UniformFull;
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]
@@ -54,3 +58,10 @@ public sealed class ClothingComponentState : ComponentState
EquippedPrefix = equippedPrefix; EquippedPrefix = equippedPrefix;
} }
} }
public enum FemaleClothingMask : byte
{
NoMask = 0,
UniformFull,
UniformTop
}

View File

@@ -52,5 +52,22 @@ public sealed class ClothingSystem : EntitySystem
Dirty(clothing); Dirty(clothing);
} }
/// <summary>
/// Copy all clothing specific visuals from another item.
/// </summary>
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 #endregion
} }

View File

@@ -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) { }
/// <summary>
/// Check if this entity prototype is valid target for chameleon item.
/// </summary>
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;
}
}

View File

@@ -18,6 +18,7 @@ public sealed class ItemComponent : Component
[Access(typeof(SharedItemSystem), Other = AccessPermissions.ReadExecute)] [Access(typeof(SharedItemSystem), Other = AccessPermissions.ReadExecute)]
public int Size = 5; public int Size = 5;
[Access(typeof(SharedItemSystem))]
[DataField("inhandVisuals")] [DataField("inhandVisuals")]
public Dictionary<HandLocation, List<SharedSpriteComponent.PrototypeLayerData>> InhandVisuals = new(); public Dictionary<HandLocation, List<SharedSpriteComponent.PrototypeLayerData>> InhandVisuals = new();
@@ -29,9 +30,10 @@ public sealed class ItemComponent : Component
/// <summary> /// <summary>
/// Rsi of the sprite shown on the player when this item is in their hands. Used to generate a default entry for <see cref="InhandVisuals"/> /// Rsi of the sprite shown on the player when this item is in their hands. Used to generate a default entry for <see cref="InhandVisuals"/>
/// </summary> /// </summary>
[Access(typeof(SharedItemSystem))]
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField("sprite")] [DataField("sprite")]
public readonly string? RsiPath; public string? RsiPath;
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]

View File

@@ -49,6 +49,22 @@ public abstract class SharedItemSystem : EntitySystem
VisualsChanged(uid); VisualsChanged(uid);
} }
/// <summary>
/// Copy all item specific visuals from another item.
/// </summary>
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 #endregion
private void OnHandInteract(EntityUid uid, ItemComponent component, InteractHandEvent args) private void OnHandInteract(EntityUid uid, ItemComponent component, InteractHandEvent args)

View File

@@ -0,0 +1,7 @@

## UI
chameleon-component-ui-window-name = Chameleon Settings
chameleon-component-ui-search-placeholder = Search...
## Verb
chameleon-component-verb-text = Chameleon

View File

@@ -8,6 +8,7 @@ fibers-durathread = durathread
fibers-latex = latex fibers-latex = latex
fibers-nitrile = nitrile fibers-nitrile = nitrile
fibers-nanomachines = insulative nanomachine fibers-nanomachines = insulative nanomachine
fibers-chameleon = holographic chameleon
fibers-purple = purple fibers-purple = purple
fibers-red = red fibers-red = red

View File

@@ -30,7 +30,7 @@
amount: 5 amount: 5
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicateMedical parent: ClothingBackpackDuffelSyndicateMedicalBundle
id: ClothingBackpackDuffelSyndicateFilledMedical id: ClothingBackpackDuffelSyndicateFilledMedical
name: syndicate surgical duffel bag 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." 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 - id: ScalpelAdvanced
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicateAmmo parent: ClothingBackpackDuffelSyndicateAmmoBundle
id: ClothingBackpackDuffelSyndicateFilledShotgun id: ClothingBackpackDuffelSyndicateFilledShotgun
name: Bulldog bundle 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. 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 # - id: ThermalImagingGoggles
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicateAmmo parent: ClothingBackpackDuffelSyndicateAmmoBundle
id: ClothingBackpackDuffelSyndicateFilledSMG id: ClothingBackpackDuffelSyndicateFilledSMG
name: C-20r bundle name: C-20r bundle
description: "Old faithful: The classic C-20r Submachine Gun, bundled with three magazines." #, and a Suppressor. description: "Old faithful: The classic C-20r Submachine Gun, bundled with three magazines." #, and a Suppressor.
@@ -71,7 +71,7 @@
# - id: SMGSuppressor # - id: SMGSuppressor
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicateAmmo parent: ClothingBackpackDuffelSyndicateAmmoBundle
id: ClothingBackpackDuffelSyndicateFilledRevolver id: ClothingBackpackDuffelSyndicateFilledRevolver
name: Python bundle name: Python bundle
description: "Go loud and proud with a fully loaded Magnum Python, bundled with two speed loaders." description: "Go loud and proud with a fully loaded Magnum Python, bundled with two speed loaders."
@@ -83,7 +83,7 @@
amount: 2 amount: 2
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicateAmmo parent: ClothingBackpackDuffelSyndicateAmmoBundle
id: ClothingBackpackDuffelSyndicateFilledLMG id: ClothingBackpackDuffelSyndicateFilledLMG
name: L6 Saw bundle name: L6 Saw bundle
description: "More dakka: The iconic L6 lightmachinegun, bundled with 2 box magazines." description: "More dakka: The iconic L6 lightmachinegun, bundled with 2 box magazines."
@@ -94,7 +94,7 @@
- id: MagazineLightRifleBox - id: MagazineLightRifleBox
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicateAmmo parent: ClothingBackpackDuffelSyndicateAmmoBundle
id: ClothingBackpackDuffelSyndicateFilledGrenadeLauncher id: ClothingBackpackDuffelSyndicateFilledGrenadeLauncher
name: China-Lake bundle name: China-Lake bundle
description: "An old China-Lake grenade launcher bundled with 8 rounds of various destruction capability." description: "An old China-Lake grenade launcher bundled with 8 rounds of various destruction capability."
@@ -108,7 +108,7 @@
amount: 4 amount: 4
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicateAmmo parent: ClothingBackpackDuffelSyndicateAmmoBundle
id: ClothingBackpackDuffelSyndicateFilledCarbine id: ClothingBackpackDuffelSyndicateFilledCarbine
name: M-90gl bundle name: M-90gl bundle
description: "A versatile battle rifle with an attached grenade launcher, bundled with 3 magazines and 6 grenades of various capabilities." 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 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." description: "Contains a full CentCom Official uniform set, headset and clipboard included. The headset comes without an encryption key."
components: components:
- type: Tag
tags: [] # ignore "WhitelistChameleon" tag
- type: StorageFill - type: StorageFill
contents: contents:
- id: ClothingHeadHatCaptain - id: ClothingHeadHatCaptain
@@ -150,6 +152,8 @@
name: clown costume duffel bag name: clown costume duffel bag
description: "Contains a complete Clown outfit." description: "Contains a complete Clown outfit."
components: components:
- type: Tag
tags: [] # ignore "WhitelistChameleon" tag
- type: StorageFill - type: StorageFill
contents: contents:
- id: ClothingUniformJumpsuitClown - id: ClothingUniformJumpsuitClown
@@ -160,7 +164,7 @@
- id: ClothingHeadsetService - id: ClothingHeadsetService
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicate parent: ClothingBackpackDuffelSyndicateBundle
id: ClothingBackpackDuffelSyndicatePyjamaBundle id: ClothingBackpackDuffelSyndicatePyjamaBundle
name: syndicate pyjama duffel bag name: syndicate pyjama duffel bag
description: "Contains 3 syndicate pyjamas." description: "Contains 3 syndicate pyjamas."
@@ -182,7 +186,7 @@
- id: PlushieLizard - id: PlushieLizard
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicate parent: ClothingBackpackDuffelSyndicateBundle
id: ClothingBackpackDuffelSyndicateC4tBundle id: ClothingBackpackDuffelSyndicateC4tBundle
name: syndicate C-4 bundle name: syndicate C-4 bundle
description: "Contains a lot of C-4 charges." description: "Contains a lot of C-4 charges."
@@ -193,7 +197,24 @@
amount: 8 amount: 8
- type: entity - 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 id: ClothingBackpackDuffelSyndicateEVABundle
name: syndicate EVA bundle name: syndicate EVA bundle
description: "Contains the Syndicate approved EVA suit." description: "Contains the Syndicate approved EVA suit."
@@ -204,7 +225,7 @@
- id: ClothingOuterHardsuitSyndicate - id: ClothingOuterHardsuitSyndicate
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicate parent: ClothingBackpackDuffelSyndicateBundle
id: ClothingBackpackDuffelSyndicateHardsuitBundle id: ClothingBackpackDuffelSyndicateHardsuitBundle
name: syndicate hardsuit bundle name: syndicate hardsuit bundle
description: "Contains the Syndicate's signature blood red hardsuit." description: "Contains the Syndicate's signature blood red hardsuit."
@@ -214,7 +235,7 @@
- id: ClothingOuterHardsuitSyndie - id: ClothingOuterHardsuitSyndie
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicate parent: ClothingBackpackDuffelSyndicateBundle
id: ClothingBackpackDuffelZombieBundle id: ClothingBackpackDuffelZombieBundle
name: syndicate zombie bundle name: syndicate zombie bundle
description: "An all-in-one kit for unleashing the undead upon a station." description: "An all-in-one kit for unleashing the undead upon a station."
@@ -228,7 +249,7 @@
amount: 3 amount: 3
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicate parent: ClothingBackpackDuffelSyndicateBundle
id: ClothingBackpackDuffelSyndicateOperative id: ClothingBackpackDuffelSyndicateOperative
name: operative duffelbag name: operative duffelbag
components: components:
@@ -241,7 +262,7 @@
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicateMedical parent: ClothingBackpackDuffelSyndicateMedicalBundle
id: ClothingBackpackDuffelSyndicateOperativeMedic id: ClothingBackpackDuffelSyndicateOperativeMedic
name: operative medic duffelbag name: operative medic duffelbag
description: A large duffel bag for holding extra medical supplies. description: A large duffel bag for holding extra medical supplies.

View File

@@ -428,7 +428,17 @@
# Armor # 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 - type: listing
id: UplinkClothingNoSlipsShoes id: UplinkClothingNoSlipsShoes
name: no-slip shoes name: no-slip shoes

View File

@@ -117,6 +117,14 @@
- type: Storage - type: Storage
capacity: 131 capacity: 131
- type: entity
parent: ClothingBackpackDuffelSyndicate
id: ClothingBackpackDuffelSyndicateBundle
abstract: true
components:
- type: Tag
tags: [] # ignore "WhitelistChameleon" tag
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicate parent: ClothingBackpackDuffelSyndicate
id: ClothingBackpackDuffelSyndicateAmmo id: ClothingBackpackDuffelSyndicateAmmo
@@ -130,6 +138,14 @@
- type: Clothing - type: Clothing
equippedPrefix: ammo equippedPrefix: ammo
- type: entity
parent: ClothingBackpackDuffelSyndicate
id: ClothingBackpackDuffelSyndicateAmmoBundle
abstract: true
components:
- type: Tag
tags: [] # ignore "WhitelistChameleon" tag
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicate parent: ClothingBackpackDuffelSyndicate
id: ClothingBackpackDuffelSyndicateMedical id: ClothingBackpackDuffelSyndicateMedical
@@ -143,6 +159,14 @@
- type: Clothing - type: Clothing
equippedPrefix: med equippedPrefix: med
- type: entity
parent: ClothingBackpackDuffelSyndicate
id: ClothingBackpackDuffelSyndicateMedicalBundle
abstract: true
components:
- type: Tag
tags: [] # ignore "WhitelistChameleon" tag
- type: entity - type: entity
parent: ClothingBackpackDuffel parent: ClothingBackpackDuffel
id: ClothingBackpackDuffelHolding id: ClothingBackpackDuffelHolding

View File

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

View File

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

View File

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

View File

@@ -242,6 +242,8 @@
name: black gloves #Intentionally named after regular gloves, they're meant to be sneaky. 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. description: Seemingly regular black gloves. The fingertips are outfitted with nanotech that makes stealing a breeze.
components: components:
- type: Tag
tags: [] # ignore "WhitelistChameleon" tag
- type: Sprite - type: Sprite
sprite: Clothing/Hands/Gloves/Color/black.rsi sprite: Clothing/Hands/Gloves/Color/black.rsi
- type: Clothing - type: Clothing

View File

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

View File

@@ -11,6 +11,7 @@
- type: Tag - type: Tag
tags: tags:
- DroneUsable - DroneUsable
- WhitelistChameleon
- type: entity - type: entity
abstract: true abstract: true
@@ -22,6 +23,9 @@
- HEAD - HEAD
- type: Sprite - type: Sprite
state: icon state: icon
- type: Tag
tags:
- WhitelistChameleon
- type: entity - type: entity
abstract: true abstract: true
@@ -78,6 +82,7 @@
- type: Tag - type: Tag
tags: tags:
- HidesHair - HidesHair
- WhitelistChameleon
- type: DiseaseProtection - type: DiseaseProtection
protection: 0.05 protection: 0.05
- type: IdentityBlocker - type: IdentityBlocker
@@ -116,6 +121,7 @@
- type: Tag - type: Tag
tags: tags:
- HidesHair - HidesHair
- WhitelistChameleon
- type: DiseaseProtection - type: DiseaseProtection
protection: 0.05 protection: 0.05
- type: IdentityBlocker - type: IdentityBlocker

View File

@@ -37,6 +37,8 @@
- type: PowerCellSlot - type: PowerCellSlot
cellSlot: cellSlot:
startingItem: PowerCellMedium startingItem: PowerCellMedium
- type: Item
heldPrefix: off
- type: ContainerContainer - type: ContainerContainer
containers: containers:
cell_slot: !type:ContainerSlot cell_slot: !type:ContainerSlot

View File

@@ -255,6 +255,7 @@
- type: Tag - type: Tag
tags: tags:
- HidesHair - HidesHair
- WhitelistChameleon
- type: entity - type: entity
parent: ClothingHeadBase parent: ClothingHeadBase

View File

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

View File

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

View File

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

View File

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

View File

@@ -101,10 +101,32 @@
sprite: Clothing/Shoes/Specific/wizard.rsi sprite: Clothing/Shoes/Specific/wizard.rsi
- type: entity - 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 id: ClothingShoesChameleonNoSlips
name: black shoes #actual name and description in uplink_catalog.yml name: black shoes #actual name and description in uplink_catalog.yml
suffix: no-slip suffix: No-slip, Chameleon
description: Stylish black shoes. description: Stylish black shoes.
components: components:
- type: NoSlip - type: NoSlip

View File

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

View File

@@ -5,3 +5,6 @@
components: components:
- type: Sprite - type: Sprite
netsync: false netsync: false
- type: Tag
tags:
- WhitelistChameleon

View File

@@ -195,6 +195,9 @@
- type: Tag - type: Tag
id: FireAxe id: FireAxe
- type: Tag
id: WhitelistChameleon
- type: Tag - type: Tag
id: FirelockElectronics id: FirelockElectronics