From f9e32fb0c1b6c06a8721944c51af8834e6f66fe9 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Mon, 7 Feb 2022 14:59:22 +1100 Subject: [PATCH] Revert "Add support for multi-layer in-hand and clothing sprites" (#6521) --- Content.Client/Clothing/ClothingSystem.cs | 221 ++++++------------ Content.Client/Hands/HandsComponent.cs | 7 +- Content.Client/Hands/HandsVisualizer.cs | 62 +++++ Content.Client/Hands/Systems/HandsSystem.cs | 100 +------- .../Inventory/ClientInventoryComponent.cs | 6 - Content.Client/Items/Systems/ItemSystem.cs | 110 --------- Content.Server/Items/ItemSystem.cs | 8 - Content.Shared/Clothing/ClothingEvents.cs | 60 ----- .../Components/HandVirtualItemComponent.cs | 3 +- .../Hands/Components/SharedHandsComponent.cs | 44 +++- Content.Shared/Hands/HandEvents.cs | 54 ----- Content.Shared/Hands/SharedHandsSystem.cs | 46 +++- .../Inventory/InventorySystem.Equip.cs | 3 - .../{SharedItemSystem.cs => ItemSystem.cs} | 13 +- Content.Shared/Item/SharedItemComponent.cs | 52 +++-- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 1 + .../Entities/Mobs/Species/human.yml | 1 + 17 files changed, 262 insertions(+), 529 deletions(-) create mode 100644 Content.Client/Hands/HandsVisualizer.cs delete mode 100644 Content.Client/Items/Systems/ItemSystem.cs delete mode 100644 Content.Server/Items/ItemSystem.cs delete mode 100644 Content.Shared/Clothing/ClothingEvents.cs rename Content.Shared/Item/{SharedItemSystem.cs => ItemSystem.cs} (88%) diff --git a/Content.Client/Clothing/ClothingSystem.cs b/Content.Client/Clothing/ClothingSystem.cs index 6c57c255b3..e630bae637 100644 --- a/Content.Client/Clothing/ClothingSystem.cs +++ b/Content.Client/Clothing/ClothingSystem.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; +using System.Collections.Generic; using Content.Client.Inventory; using Content.Shared.CharacterAppearance; using Content.Shared.Clothing; @@ -11,9 +9,8 @@ using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; using Robust.Shared.IoC; -using Robust.Shared.Log; -using static Robust.Shared.GameObjects.SharedSpriteComponent; namespace Content.Client.Clothing; @@ -51,94 +48,11 @@ public class ClothingSystem : EntitySystem SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnGotUnequipped); - - SubscribeLocalEvent(OnGetVisuals); - - SubscribeLocalEvent(OnVisualsChanged); + SubscribeLocalEvent(OnPrefixChanged); SubscribeLocalEvent(OnDidUnequip); } - private void OnGetVisuals(EntityUid uid, SharedItemComponent item, GetEquipmentVisualsEvent args) - { - if (!TryComp(args.Equipee, out ClientInventoryComponent? inventory)) - return; - - List? layers = null; - - // first attempt to get species specific data. - if (inventory.SpeciesId != null) - item.ClothingVisuals.TryGetValue($"{args.Slot}-{inventory.SpeciesId}", out layers); - - // if that returned nothing, attempt to find generic data - if (layers == null && !item.ClothingVisuals.TryGetValue(args.Slot, out layers)) - { - // No generic data either. Attempt to generate defaults from the item's RSI & item-prefixes - if (!TryGetDefaultVisuals(uid, item, args.Slot, inventory.SpeciesId, out layers)) - return; - } - - // add each layer to the visuals - var i = 0; - foreach (var layer in layers) - { - var key = layer.MapKeys?.FirstOrDefault(); - if (key == null) - { - key = i == 0 ? args.Slot : $"{args.Slot}-{i}"; - i++; - } - - args.Layers.Add((key, layer)); - } - } - - /// - /// If no explicit clothing visuals were specified, this attempts to populate with default values. - /// - /// - /// Useful for lazily adding clothing sprites without modifying yaml. And for backwards compatibility. - /// - private bool TryGetDefaultVisuals(EntityUid uid, SharedItemComponent item, string slot, string? speciesId, - [NotNullWhen(true)] out List? layers) - { - layers = null; - - RSI? rsi = null; - - if (item.RsiPath != null) - rsi = _cache.GetResource(TextureRoot / item.RsiPath).RSI; - else if (TryComp(uid, out SpriteComponent? sprite)) - rsi = sprite.BaseRSI; - - if (rsi == null || rsi.Path == null) - return false; - - var correctedSlot = slot; - TemporarySlotMap.TryGetValue(correctedSlot, out correctedSlot); - - var state = (item.EquippedPrefix == null) - ? $"equipped-{correctedSlot}" - : $"{item.EquippedPrefix}-equipped-{correctedSlot}"; - - // species specific - if (speciesId != null && rsi.TryGetState($"{state}-{speciesId}", out _)) - { - state = $"{state}-{speciesId}"; - } - else if (!rsi.TryGetState(state, out _)) - { - return false; - } - - var layer = PrototypeLayerData.New(); - layer.RsiPath = rsi.Path.ToString(); - layer.State = state; - layers = new() { layer }; - - return true; - } - - private void OnVisualsChanged(EntityUid uid, ClientInventoryComponent component, VisualsChangedEvent args) + private void OnPrefixChanged(EntityUid uid, ClientInventoryComponent component, ItemPrefixChangeEvent args) { if (!TryComp(args.Item, out ClothingComponent? clothing) || clothing.InSlot == null) return; @@ -153,19 +67,7 @@ public class ClothingSystem : EntitySystem private void OnDidUnequip(EntityUid uid, SpriteComponent component, DidUnequipEvent args) { - if (!TryComp(uid, out ClientInventoryComponent? inventory) || !TryComp(uid, out SpriteComponent? sprite)) - return; - - if (!inventory.VisualLayerKeys.TryGetValue(args.Slot, out var revealedLayers)) - return; - - // Remove old layers. We could also just set them to invisible, but as items may add arbitrary layers, this - // may eventually bloat the player with lots of invisible layers. - foreach (var layer in revealedLayers) - { - sprite.RemoveLayer(layer); - } - revealedLayers.Clear(); + component.LayerSetVisible(args.Slot, false); } public void InitClothing(EntityUid uid, ClientInventoryComponent? component = null, SpriteComponent? sprite = null) @@ -174,6 +76,8 @@ public class ClothingSystem : EntitySystem foreach (var slot in slots) { + sprite.LayerMapReserveBlank(slot.Name); + if (!_inventorySystem.TryGetSlotContainer(uid, slot.Name, out var containerSlot, out _, component) || !containerSlot.ContainedEntity.HasValue) continue; @@ -185,15 +89,55 @@ public class ClothingSystem : EntitySystem { component.InSlot = args.Slot; - RenderEquipment(args.Equipee, uid, args.Slot, clothingComponent: component); + if (!TryComp(args.Equipee, out var sprite) || !TryComp(args.Equipee, out var invComp)) + { + return; + } + + var data = GetEquippedStateInfo(args.Equipment, args.Slot, invComp.SpeciesId, component); + if (data != null) + { + var (rsi, state) = data.Value; + sprite.LayerSetVisible(args.Slot, true); + sprite.LayerSetState(args.Slot, state, rsi); + sprite.LayerSetAutoAnimated(args.Slot, true); + + if (args.Slot == "jumpsuit" && sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out _)) + { + sprite.LayerSetState(HumanoidVisualLayers.StencilMask, component.FemaleMask switch + { + FemaleClothingMask.NoMask => "female_none", + FemaleClothingMask.UniformTop => "female_top", + _ => "female_full", + }); + } + + return; + } + + + sprite.LayerSetVisible(args.Slot, false); } - private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot, - ClientInventoryComponent? inventory = null, SpriteComponent? sprite = null, ClothingComponent? clothingComponent = null) + private void RenderEquipment(EntityUid uid, EntityUid equipment, string slot, + ClientInventoryComponent? inventoryComponent = null, SpriteComponent? sprite = null, ClothingComponent? clothingComponent = null) { - if(!Resolve(equipee, ref inventory, ref sprite) || !Resolve(equipment, ref clothingComponent, false)) + if(!Resolve(uid, ref inventoryComponent, ref sprite)) return; + if (!Resolve(equipment, ref clothingComponent, false)) + { + sprite.LayerSetVisible(slot, false); + return; + } + + var data = GetEquippedStateInfo(equipment, slot, inventoryComponent.SpeciesId, clothingComponent); + if (data == null) return; + var (rsi, state) = data.Value; + sprite.LayerSetVisible(slot, true); + sprite.LayerSetState(slot, state, rsi); + sprite.LayerSetAutoAnimated(slot, true); + if (slot == "jumpsuit" && sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out _)) { sprite.LayerSetState(HumanoidVisualLayers.StencilMask, clothingComponent.FemaleMask switch @@ -203,55 +147,34 @@ public class ClothingSystem : EntitySystem _ => "female_full", }); } + } - // Remove old layers. We could also just set them to invisible, but as items may add arbitrary layers, this - // may eventually bloat the player with lots of invisible layers. - if (inventory.VisualLayerKeys.TryGetValue(slot, out var revealedLayers)) + public (RSI rsi, RSI.StateId stateId)? GetEquippedStateInfo(EntityUid uid, string slot, string? speciesId=null, ClothingComponent? component = null) + { + if (!Resolve(uid, ref component)) + return null; + + if (component.RsiPath == null) + return null; + + var rsi = _cache.GetResource(SharedSpriteComponent.TextureRoot / component.RsiPath).RSI; + var correctedSlot = slot; + TemporarySlotMap.TryGetValue(correctedSlot, out correctedSlot); + var stateId = component.EquippedPrefix != null ? $"{component.EquippedPrefix}-equipped-{correctedSlot}" : $"equipped-{correctedSlot}"; + if (speciesId != null) { - foreach (var key in revealedLayers) + var speciesState = $"{stateId}-{speciesId}"; + if (rsi.TryGetState(speciesState, out _)) { - sprite.RemoveLayer(key); + return (rsi, speciesState); } - revealedLayers.Clear(); } - else + + if (rsi.TryGetState(stateId, out _)) { - revealedLayers = new(); - inventory.VisualLayerKeys[slot] = revealedLayers; + return (rsi, stateId); } - var ev = new GetEquipmentVisualsEvent(equipee, slot); - RaiseLocalEvent(equipment, ev, false); - - if (ev.Layers.Count == 0) - { - RaiseLocalEvent(equipment, new EquipmentVisualsUpdatedEvent(equipee, slot, revealedLayers)); - return; - } - - // add the new layers - foreach (var (key, layerData) in ev.Layers) - { - if (!revealedLayers.Add(key)) - { - Logger.Warning($"Duplicate key for clothing visuals: {key}. Are multiple components attempting to modify the same layer? Equipment: {ToPrettyString(equipment)}"); - continue; - } - - var index = sprite.LayerMapReserveBlank(key); - - // In case no RSI is given, use the item's base RSI as a default. This cuts down on a lot of unnecessary yaml entries. - if (layerData.RsiPath == null - && layerData.TexturePath == null - && sprite[index].Rsi == null - && TryComp(equipment, out SpriteComponent? clothingSprite)) - { - sprite.LayerSetRSI(index, clothingSprite.BaseRSI); - } - - sprite.LayerSetData(index, layerData); - } - - RaiseLocalEvent(equipment, new EquipmentVisualsUpdatedEvent(equipee, slot, revealedLayers)); + return null; } } diff --git a/Content.Client/Hands/HandsComponent.cs b/Content.Client/Hands/HandsComponent.cs index c2c8cde42b..2927ba97bc 100644 --- a/Content.Client/Hands/HandsComponent.cs +++ b/Content.Client/Hands/HandsComponent.cs @@ -1,7 +1,7 @@ using Content.Shared.Hands.Components; using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; -using System.Collections.Generic; +using Robust.Shared.IoC; namespace Content.Client.Hands { @@ -11,10 +11,5 @@ namespace Content.Client.Hands public class HandsComponent : SharedHandsComponent { public HandsGui? Gui { get; set; } - - /// - /// Data about the current sprite layers that the hand is contributing to the owner entity. Used for sprite in-hands. - /// - public readonly Dictionary> RevealedLayers = new(); } } diff --git a/Content.Client/Hands/HandsVisualizer.cs b/Content.Client/Hands/HandsVisualizer.cs new file mode 100644 index 0000000000..38704a05d1 --- /dev/null +++ b/Content.Client/Hands/HandsVisualizer.cs @@ -0,0 +1,62 @@ +using System; +using Content.Shared.Hands.Components; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Client.ResourceManagement; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Content.Client.Hands +{ + [UsedImplicitly] + public class HandsVisualizer : AppearanceVisualizer + { + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + var entities = IoCManager.Resolve(); + if (!entities.TryGetComponent(component.Owner, out var sprite)) return; + if (!component.TryGetData(HandsVisuals.VisualState, out HandsVisualState visualState)) return; + + foreach (HandLocation location in Enum.GetValues(typeof(HandLocation))) + { + var layerKey = LocationToLayerKey(location); + if (sprite.LayerMapTryGet(layerKey, out var layer)) + { + sprite.RemoveLayer(layer); + sprite.LayerMapRemove(layer); + } + } + + var resourceCache = IoCManager.Resolve(); + var hands = visualState.Hands; + + foreach (var hand in hands) + { + var rsi = resourceCache.GetResource(SharedSpriteComponent.TextureRoot / hand.RsiPath).RSI; + + var state = $"inhand-{hand.Location.ToString().ToLowerInvariant()}"; + if (hand.EquippedPrefix != null) + state = $"{hand.EquippedPrefix}-" + state; + + if (rsi.TryGetState(state, out var _)) + { + var layerKey = LocationToLayerKey(hand.Location); + sprite.LayerMapReserveBlank(layerKey); + + var layer = sprite.LayerMapGet(layerKey); + sprite.LayerSetVisible(layer, true); + sprite.LayerSetRSI(layer, rsi); + sprite.LayerSetColor(layer, hand.Color); + sprite.LayerSetState(layer, state); + } + } + } + + private string LocationToLayerKey(HandLocation location) + { + return location.ToString(); + } + } +} diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs index 7f3e236629..fcc7fe3722 100644 --- a/Content.Client/Hands/Systems/HandsSystem.cs +++ b/Content.Client/Hands/Systems/HandsSystem.cs @@ -4,7 +4,6 @@ using Content.Client.Animations; using Content.Client.HUD; using Content.Shared.Hands; using Content.Shared.Hands.Components; -using Content.Shared.Item; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Player; @@ -12,7 +11,6 @@ using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; -using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Timing; @@ -34,7 +32,6 @@ namespace Content.Client.Hands SubscribeLocalEvent(HandlePlayerDetached); SubscribeLocalEvent(HandleCompRemove); SubscribeLocalEvent(HandleComponentState); - SubscribeLocalEvent(OnVisualsChanged); SubscribeNetworkEvent(HandlePickupAnimation); } @@ -183,95 +180,6 @@ namespace Content.Client.Hands RaiseNetworkEvent(new ActivateInHandMsg(handName)); } - #region visuals - protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent handComp, ContainerModifiedMessage args) - { - if (handComp.TryGetHand(args.Container.ID, out var hand)) - { - UpdateHandVisuals(uid, args.Entity, hand); - } - } - - /// - /// Update the players sprite with new in-hand visuals. - /// - private void UpdateHandVisuals(EntityUid uid, EntityUid held, Hand hand, HandsComponent? handComp = null, SpriteComponent? sprite = null) - { - if (!Resolve(uid, ref handComp, ref sprite, false)) - return; - - if (uid == _playerManager.LocalPlayer?.ControlledEntity) - UpdateGui(); - - // Remove old layers. We could also just set them to invisible, but as items may add arbitrary layers, this - // may eventually bloat the player with lots of layers. - if (handComp.RevealedLayers.TryGetValue(hand.Location, out var revealedLayers)) - { - foreach (var key in revealedLayers) - { - sprite.RemoveLayer(key); - } - revealedLayers.Clear(); - } - else - { - revealedLayers = new(); - handComp.RevealedLayers[hand.Location] = revealedLayers; - } - - if (hand.HeldEntity == null) - { - // the held item was removed. - RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers)); - return; - } - - var ev = new GetInhandVisualsEvent(uid, hand.Location); - RaiseLocalEvent(held, ev, false); - - if (ev.Layers.Count == 0) - { - RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers)); - return; - } - - // add the new layers - foreach (var (key, layerData) in ev.Layers) - { - if (!revealedLayers.Add(key)) - { - Logger.Warning($"Duplicate key for in-hand visuals: {key}. Are multiple components attempting to modify the same layer? Entity: {ToPrettyString(held)}"); - continue; - } - - var index = sprite.LayerMapReserveBlank(key); - - // In case no RSI is given, use the item's base RSI as a default. This cuts down on a lot of unnecessary yaml entries. - if (layerData.RsiPath == null - && layerData.TexturePath == null - && sprite[index].Rsi == null - && TryComp(held, out SpriteComponent? clothingSprite)) - { - sprite.LayerSetRSI(index, clothingSprite.BaseRSI); - } - - sprite.LayerSetData(index, layerData); - } - - RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers)); - } - - private void OnVisualsChanged(EntityUid uid, HandsComponent component, VisualsChangedEvent args) - { - // update hands visuals if this item is in a hand (rather then inventory or other container). - if (component.TryGetHand(args.ContainerId, out var hand)) - { - UpdateHandVisuals(uid, args.Item, hand, component); - } - } - #endregion - - #region Gui public void UpdateGui(HandsComponent? hands = null) { @@ -285,6 +193,14 @@ namespace Content.Client.Hands hands.Gui.Update(new HandsGuiState(states, hands.ActiveHand)); } + public override void UpdateHandVisuals(EntityUid uid, SharedHandsComponent? handComp = null, AppearanceComponent? appearance = null) + { + base.UpdateHandVisuals(uid, handComp, appearance); + + if (uid == _playerManager.LocalPlayer?.ControlledEntity) + UpdateGui(); + } + public override bool TrySetActiveHand(EntityUid uid, string? value, SharedHandsComponent? handComp = null) { if (!base.TrySetActiveHand(uid, value, handComp)) diff --git a/Content.Client/Inventory/ClientInventoryComponent.cs b/Content.Client/Inventory/ClientInventoryComponent.cs index 3bfd6221dc..010ac12ff4 100644 --- a/Content.Client/Inventory/ClientInventoryComponent.cs +++ b/Content.Client/Inventory/ClientInventoryComponent.cs @@ -30,12 +30,6 @@ namespace Content.Client.Inventory [ViewVariables] [DataField("speciesId")] public string? SpeciesId { get; set; } - /// - /// Data about the current layers that have been added to the players sprite due to the items in each equipment slot. - /// - [ViewVariables] - public readonly Dictionary> VisualLayerKeys = new(); - public bool AttachedToGameHud; } } diff --git a/Content.Client/Items/Systems/ItemSystem.cs b/Content.Client/Items/Systems/ItemSystem.cs deleted file mode 100644 index 32328ea664..0000000000 --- a/Content.Client/Items/Systems/ItemSystem.cs +++ /dev/null @@ -1,110 +0,0 @@ -using Content.Shared.Hands; -using Content.Shared.Hands.Components; -using Content.Shared.Item; -using Robust.Client.GameObjects; -using Robust.Client.Graphics; -using Robust.Client.ResourceManagement; -using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using static Robust.Shared.GameObjects.SharedSpriteComponent; - -namespace Content.Client.Items.Systems; - -public sealed class ItemSystem : SharedItemSystem -{ - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly IResourceCache _resCache = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnGetVisuals); - } - - #region InhandVisuals - /// - /// When an items visual state changes, notify and entities that are holding this item that their sprite may need updating. - /// - public override void VisualsChanged(EntityUid uid, SharedItemComponent? item = null) - { - if (!Resolve(uid, ref item)) - return; - - // if the item is in a container, it might be equipped to hands or inventory slots --> update visuals. - if (_containerSystem.TryGetContainingContainer(uid, out var container)) - RaiseLocalEvent(container.Owner, new VisualsChangedEvent(uid, container.ID)); - } - - /// - /// An entity holding this item is requesting visual information for in-hand sprites. - /// - private void OnGetVisuals(EntityUid uid, SharedItemComponent item, GetInhandVisualsEvent args) - { - var defaultKey = $"inhand-{args.Location.ToString().ToLowerInvariant()}"; - - // try get explicit visuals - if (item.InhandVisuals == null || !item.InhandVisuals.TryGetValue(args.Location, out var layers)) - { - // get defaults - if (!TryGetDefaultVisuals(uid, item, defaultKey, out layers)) - return; - } - - var i = 0; - foreach (var layer in layers) - { - var key = layer.MapKeys?.FirstOrDefault(); - if (key == null) - { - key = i == 0 ? defaultKey : $"{defaultKey}-{i}"; - i++; - } - - args.Layers.Add((key, layer)); - } - } - - /// - /// If no explicit in-hand visuals were specified, this attempts to populate with default values. - /// - /// - /// Useful for lazily adding in-hand sprites without modifying yaml. And backwards compatibility. - /// - private bool TryGetDefaultVisuals(EntityUid uid, SharedItemComponent item, string defaultKey, [NotNullWhen(true)] out List? result) - { - result = null; - - RSI? rsi = null; - - if (item.RsiPath != null) - rsi = _resCache.GetResource(TextureRoot / item.RsiPath).RSI; - else if (TryComp(uid, out SpriteComponent? sprite)) - rsi = sprite.BaseRSI; - - if (rsi == null || rsi.Path == null) - return false; - - var state = (item.EquippedPrefix == null) - ? defaultKey - : $"{item.EquippedPrefix}-{defaultKey}"; - - if (!rsi.TryGetState(state, out var _)) - return false; - - var layer = PrototypeLayerData.New(); - layer.RsiPath = rsi.Path.ToString(); - layer.State = state; - layer.MapKeys = new() { state }; - - result = new() { layer }; - return true; - } - #endregion -} diff --git a/Content.Server/Items/ItemSystem.cs b/Content.Server/Items/ItemSystem.cs deleted file mode 100644 index 51e62044db..0000000000 --- a/Content.Server/Items/ItemSystem.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Content.Shared.Item; - -namespace Content.Server.Item; - -public sealed class ItemSystem : SharedItemSystem -{ - // Ello Guvna -} diff --git a/Content.Shared/Clothing/ClothingEvents.cs b/Content.Shared/Clothing/ClothingEvents.cs deleted file mode 100644 index 3b38719b18..0000000000 --- a/Content.Shared/Clothing/ClothingEvents.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using Robust.Shared.GameObjects; -using static Robust.Shared.GameObjects.SharedSpriteComponent; - -namespace Content.Shared.Clothing; - -/// -/// Raised directed at a piece of clothing to get the set of layers to show on the wearer's sprite -/// -public class GetEquipmentVisualsEvent : EntityEventArgs -{ - /// - /// Entity that is wearing the item. - /// - public readonly EntityUid Equipee; - - public readonly string Slot; - - /// - /// The layers that will be added to the entity that is wearing this item. - /// - /// - /// Note that the actual ordering of the layers depends on the order in which they are added to this list; - /// - public List<(string, PrototypeLayerData)> Layers = new(); - - public GetEquipmentVisualsEvent(EntityUid equipee, string slot) - { - Equipee = equipee; - Slot = slot; - } -} - -/// -/// Raised directed at a piece of clothing after its visuals have been updated. -/// -/// -/// Useful for systems/components that modify the visual layers that an item adds to a player. (e.g. RGB memes) -/// -public class EquipmentVisualsUpdatedEvent : EntityEventArgs -{ - /// - /// Entity that is wearing the item. - /// - public readonly EntityUid Equipee; - - public readonly string Slot; - - /// - /// The layers that this item is now revealing. - /// - public HashSet RevealedLayers; - - public EquipmentVisualsUpdatedEvent(EntityUid equipee, string slot, HashSet revealedLayers) - { - Equipee = equipee; - Slot = slot; - RevealedLayers = revealedLayers; - } -} diff --git a/Content.Shared/Hands/Components/HandVirtualItemComponent.cs b/Content.Shared/Hands/Components/HandVirtualItemComponent.cs index 7603fd623e..2574137739 100644 --- a/Content.Shared/Hands/Components/HandVirtualItemComponent.cs +++ b/Content.Shared/Hands/Components/HandVirtualItemComponent.cs @@ -1,5 +1,4 @@ using System; -using Content.Shared.Item; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; @@ -41,7 +40,7 @@ namespace Content.Shared.Hands.Components // update hands GUI with new entity. if (Owner.TryGetContainer(out var containter)) - EntitySystem.Get().VisualsChanged(Owner); + EntitySystem.Get().UpdateHandVisuals(containter.Owner); } [Serializable, NetSerializable] diff --git a/Content.Shared/Hands/Components/SharedHandsComponent.cs b/Content.Shared/Hands/Components/SharedHandsComponent.cs index 3472889bca..448ad6d4bb 100644 --- a/Content.Shared/Hands/Components/SharedHandsComponent.cs +++ b/Content.Shared/Hands/Components/SharedHandsComponent.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.ActionBlocker; +using Content.Shared.Administration.Logs; +using Content.Shared.Database; using Content.Shared.Interaction; using Content.Shared.Item; using Robust.Shared.Containers; @@ -16,7 +18,6 @@ using Robust.Shared.Physics; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; -using static Robust.Shared.GameObjects.SharedSpriteComponent; namespace Content.Shared.Hands.Components { @@ -707,6 +708,47 @@ namespace Content.Shared.Hands.Components } } + #region visualizerData + [Serializable, NetSerializable] + public enum HandsVisuals : byte + { + VisualState + } + + [Serializable, NetSerializable] + public class HandsVisualState : ICloneable + { + public List Hands { get; } = new(); + + public HandsVisualState(List hands) + { + Hands = hands; + } + + public object Clone() + { + return new HandsVisualState(new List(Hands)); + } + } + + [Serializable, NetSerializable] + public struct HandVisualState + { + public string RsiPath { get; } + public string? EquippedPrefix { get; } + public HandLocation Location { get; } + public Color Color { get; } + + public HandVisualState(string rsiPath, string? equippedPrefix, HandLocation location, Color color) + { + RsiPath = rsiPath; + EquippedPrefix = equippedPrefix; + Location = location; + Color = color; + } + } + #endregion + [Serializable, NetSerializable] public class Hand { diff --git a/Content.Shared/Hands/HandEvents.cs b/Content.Shared/Hands/HandEvents.cs index 17655cac17..a2c727cf47 100644 --- a/Content.Shared/Hands/HandEvents.cs +++ b/Content.Shared/Hands/HandEvents.cs @@ -1,67 +1,13 @@ using System; -using System.Collections.Generic; using Content.Shared.Hands.Components; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Serialization; -using static Robust.Shared.GameObjects.SharedSpriteComponent; namespace Content.Shared.Hands { - /// - /// Raised directed at an item that needs to update its in-hand sprites/layers. - /// - public class GetInhandVisualsEvent : EntityEventArgs - { - /// - /// Entity that owns the hand holding the item. - /// - public readonly EntityUid User; - - public readonly HandLocation Location; - - /// - /// The layers that will be added to the entity that is holding this item. - /// - /// - /// Note that the actual ordering of the layers depends on the order in which they are added to this list; - /// - public List<(string, PrototypeLayerData)> Layers = new(); - - public GetInhandVisualsEvent(EntityUid user, HandLocation location) - { - User = user; - Location = location; - } - } - - /// - /// Raised directed at an item after its visuals have been updated. - /// - /// - /// Useful for systems/components that modify the visual layers that an item adds to a player. (e.g. RGB memes) - /// - public class HeldVisualsUpdatedEvent : EntityEventArgs - { - /// - /// Entity that is holding the item. - /// - public readonly EntityUid User; - - /// - /// The layers that this item is now revealing. - /// - public HashSet RevealedLayers; - - public HeldVisualsUpdatedEvent(EntityUid user, HashSet revealedLayers) - { - User = user; - RevealedLayers = revealedLayers; - } - } - /// /// Raised when an entity item in a hand is deselected. /// diff --git a/Content.Shared/Hands/SharedHandsSystem.cs b/Content.Shared/Hands/SharedHandsSystem.cs index f642f9da1e..83804fb8e0 100644 --- a/Content.Shared/Hands/SharedHandsSystem.cs +++ b/Content.Shared/Hands/SharedHandsSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Hands.Components; using Content.Shared.Input; +using Content.Shared.Item; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Input.Binding; @@ -10,6 +11,7 @@ using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Players; +using System.Collections.Generic; namespace Content.Shared.Hands { @@ -23,7 +25,8 @@ namespace Content.Shared.Hands SubscribeAllEvent(HandleSetHand); SubscribeLocalEvent(HandleContainerRemoved); - SubscribeLocalEvent(HandleContainerInserted); + SubscribeLocalEvent(HandleContainerModified); + SubscribeLocalEvent(OnPrefixChanged); CommandBinds.Builder .Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed)) @@ -31,6 +34,15 @@ namespace Content.Shared.Hands .Register(); } + private void OnPrefixChanged(EntityUid uid, SharedHandsComponent component, ItemPrefixChangeEvent args) + { + // update hands visuals if this item is in a hand (rather then inventory or other container). + if (component.HasHand(args.ContainerId)) + { + UpdateHandVisuals(uid, component); + } + } + public override void Shutdown() { base.Shutdown(); @@ -124,25 +136,43 @@ namespace Content.Shared.Hands public abstract void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, EntityUid? exclude); - #endregion protected virtual void HandleContainerRemoved(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args) { HandleContainerModified(uid, component, args); } + #endregion - protected virtual void HandleContainerModified(EntityUid uid, SharedHandsComponent hands, ContainerModifiedMessage args) + #region visuals + private void HandleContainerModified(EntityUid uid, SharedHandsComponent hands, ContainerModifiedMessage args) { - // client updates hand visuals here. + UpdateHandVisuals(uid, hands); } - private void HandleContainerInserted(EntityUid uid, SharedHandsComponent component, EntInsertedIntoContainerMessage args) + /// + /// Update the In-Hand sprites + /// + public virtual void UpdateHandVisuals(EntityUid uid, SharedHandsComponent? handComp = null, AppearanceComponent? appearance = null) { - // un-rotate entities. needed for things like directional flashlights - Transform(args.Entity).LocalRotation = 0; + if (!Resolve(uid, ref handComp, ref appearance, false)) + return; - HandleContainerModified(uid, component, args); + var handsVisuals = new List(); + foreach (var hand in handComp.Hands) + { + if (hand.HeldEntity == null) + continue; + + if (!TryComp(hand.HeldEntity.Value, out SharedItemComponent? item) || item.RsiPath == null) + continue; + + var handState = new HandVisualState(item.RsiPath, item.EquippedPrefix, hand.Location, item.Color); + handsVisuals.Add(handState); + } + + appearance.SetData(HandsVisuals.VisualState, new HandsVisualState(handsVisuals)); } + #endregion private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs) { diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index a2aa080df7..2b25abda1e 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -53,9 +53,6 @@ public abstract partial class InventorySystem if(!TryGetSlot(uid, args.Container.ID, out var slotDef, inventory: component)) return; - // un-rotate entities. needed for things like directional flashlights on hardsuit helmets - Transform(args.Entity).LocalRotation = 0; - var equippedEvent = new DidEquipEvent(uid, args.Entity, slotDef); RaiseLocalEvent(uid, equippedEvent); diff --git a/Content.Shared/Item/SharedItemSystem.cs b/Content.Shared/Item/ItemSystem.cs similarity index 88% rename from Content.Shared/Item/SharedItemSystem.cs rename to Content.Shared/Item/ItemSystem.cs index f31cec387e..1f05a229b0 100644 --- a/Content.Shared/Item/SharedItemSystem.cs +++ b/Content.Shared/Item/ItemSystem.cs @@ -4,11 +4,10 @@ using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Localization; -using System; namespace Content.Shared.Item { - public abstract class SharedItemSystem : EntitySystem + public class ItemSystem : EntitySystem { public override void Initialize() { @@ -29,11 +28,13 @@ namespace Content.Shared.Item component.Size = state.Size; component.EquippedPrefix = state.EquippedPrefix; + component.Color = state.Color; + component.RsiPath = state.RsiPath; } private void OnGetState(EntityUid uid, SharedItemComponent component, ref ComponentGetState args) { - args.State = new ItemComponentState(component.Size, component.EquippedPrefix); + args.State = new ItemComponentState(component.Size, component.EquippedPrefix, component.Color, component.RsiPath); } private void OnUnequipped(EntityUid uid, SharedSpriteComponent component, GotUnequippedEvent args) @@ -69,11 +70,5 @@ namespace Content.Shared.Item args.Verbs.Add(verb); } - - /// - /// Notifies any entity that is holding or wearing this item that they may need to update their sprite. - /// - public virtual void VisualsChanged(EntityUid owner, SharedItemComponent? item = null) - { } } } diff --git a/Content.Shared/Item/SharedItemComponent.cs b/Content.Shared/Item/SharedItemComponent.cs index d22efaa67f..b3196a86c9 100644 --- a/Content.Shared/Item/SharedItemComponent.cs +++ b/Content.Shared/Item/SharedItemComponent.cs @@ -1,10 +1,11 @@ using System; -using System.Collections.Generic; +using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Content.Shared.Inventory; using Content.Shared.Sound; +using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; @@ -12,7 +13,6 @@ using Robust.Shared.Maths; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; -using static Robust.Shared.GameObjects.SharedSpriteComponent; namespace Content.Shared.Item { @@ -40,18 +40,10 @@ namespace Content.Shared.Item [DataField("size")] private int _size; - [DataField("inhandVisuals")] - public Dictionary> InhandVisuals = new(); - - [DataField("clothingVisuals")] - public Dictionary> ClothingVisuals = new(); - /// - /// Part of the state of the sprite shown on the player when this item is in their hands or inventory. + /// Part of the state of the sprite shown on the player when this item is in their hands. /// - /// - /// Only used if or are unspecified. - /// + // todo paul make this update slotvisuals on client on change [ViewVariables(VVAccess.ReadWrite)] public string? EquippedPrefix { @@ -59,7 +51,7 @@ namespace Content.Shared.Item set { _equippedPrefix = value; - EntitySystem.Get().VisualsChanged(Owner, this); + OnEquippedPrefixChange(); Dirty(); } } @@ -73,7 +65,6 @@ namespace Content.Shared.Item [DataField("EquipSound")] public SoundSpecifier? EquipSound { get; set; } = default!; - // TODO REMOVE. Currently nonfunctional and only used by RGB system. #6253 Fixes this but requires #6252 /// /// Color of the sprite shown on the player when this item is in their hands. /// @@ -91,11 +82,20 @@ namespace Content.Shared.Item private Color _color = Color.White; /// - /// Rsi of the sprite shown on the player when this item is in their hands. Used to generate a default entry for + /// Rsi of the sprite shown on the player when this item is in their hands. /// [ViewVariables(VVAccess.ReadWrite)] + public string? RsiPath + { + get => _rsiPath; + set + { + _rsiPath = value; + Dirty(); + } + } [DataField("sprite")] - public readonly string? RsiPath; + private string? _rsiPath; bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) { @@ -116,6 +116,12 @@ namespace Content.Shared.Item return hands.TryPickupEntityToActiveHand(Owner, animateUser: true); } + private void OnEquippedPrefixChange() + { + if (Owner.TryGetContainer(out var container)) + _entMan.EventBus.RaiseLocalEvent(container.Owner, new ItemPrefixChangeEvent(Owner, container.ID)); + } + public void RemovedFromSlot() { if (_entMan.TryGetComponent(Owner, out SharedSpriteComponent component)) @@ -134,25 +140,29 @@ namespace Content.Shared.Item { public int Size { get; } public string? EquippedPrefix { get; } + public Color Color { get; } + public string? RsiPath { get; } - public ItemComponentState(int size, string? equippedPrefix) + public ItemComponentState(int size, string? equippedPrefix, Color color, string? rsiPath) { Size = size; EquippedPrefix = equippedPrefix; + Color = color; + RsiPath = rsiPath; } } /// - /// Raised when an item's visual state is changed. The event is directed at the entity that contains this item, so - /// that it can properly update its hands or inventory sprites and GUI. + /// Raised when an item's EquippedPrefix is changed. The event is directed at the entity that contains this item, so + /// that it can properly update its sprite/GUI. /// [Serializable, NetSerializable] - public class VisualsChangedEvent : EntityEventArgs + public class ItemPrefixChangeEvent : EntityEventArgs { public readonly EntityUid Item; public readonly string ContainerId; - public VisualsChangedEvent(EntityUid item, string containerId) + public ItemPrefixChangeEvent(EntityUid item, string containerId) { Item = item; ContainerId = containerId; diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 692c1152c8..9ff26f50f4 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -502,6 +502,7 @@ normal: monkey crit: dead dead: dead + - type: HandsVisualizer - type: FireVisualizer sprite: Mobs/Effects/onfire.rsi normalState: Monkey_burning diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 193dae8395..a95472b3be 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -243,6 +243,7 @@ fireStackAlternateState: 3 - type: CreamPiedVisualizer state: creampie_human + - type: HandsVisualizer - type: DamageVisualizer thresholds: [20, 40, 100] targetLayers: