Revert "Add support for multi-layer in-hand and clothing sprites" (#6521)

This commit is contained in:
metalgearsloth
2022-02-07 14:59:22 +11:00
committed by GitHub
parent 470e4f8bdc
commit f9e32fb0c1
17 changed files with 262 additions and 529 deletions

View File

@@ -1,6 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.Inventory; using Content.Client.Inventory;
using Content.Shared.CharacterAppearance; using Content.Shared.CharacterAppearance;
using Content.Shared.Clothing; using Content.Shared.Clothing;
@@ -11,9 +9,8 @@ using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.ResourceManagement; using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log;
using static Robust.Shared.GameObjects.SharedSpriteComponent;
namespace Content.Client.Clothing; namespace Content.Client.Clothing;
@@ -51,94 +48,11 @@ public class ClothingSystem : EntitySystem
SubscribeLocalEvent<ClothingComponent, GotEquippedEvent>(OnGotEquipped); SubscribeLocalEvent<ClothingComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<ClothingComponent, GotUnequippedEvent>(OnGotUnequipped); SubscribeLocalEvent<ClothingComponent, GotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<ClientInventoryComponent, ItemPrefixChangeEvent>(OnPrefixChanged);
SubscribeLocalEvent<SharedItemComponent, GetEquipmentVisualsEvent>(OnGetVisuals);
SubscribeLocalEvent<ClientInventoryComponent, VisualsChangedEvent>(OnVisualsChanged);
SubscribeLocalEvent<SpriteComponent, DidUnequipEvent>(OnDidUnequip); SubscribeLocalEvent<SpriteComponent, DidUnequipEvent>(OnDidUnequip);
} }
private void OnGetVisuals(EntityUid uid, SharedItemComponent item, GetEquipmentVisualsEvent args) private void OnPrefixChanged(EntityUid uid, ClientInventoryComponent component, ItemPrefixChangeEvent args)
{
if (!TryComp(args.Equipee, out ClientInventoryComponent? inventory))
return;
List<PrototypeLayerData>? 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));
}
}
/// <summary>
/// If no explicit clothing visuals were specified, this attempts to populate with default values.
/// </summary>
/// <remarks>
/// Useful for lazily adding clothing sprites without modifying yaml. And for backwards compatibility.
/// </remarks>
private bool TryGetDefaultVisuals(EntityUid uid, SharedItemComponent item, string slot, string? speciesId,
[NotNullWhen(true)] out List<PrototypeLayerData>? layers)
{
layers = null;
RSI? rsi = null;
if (item.RsiPath != null)
rsi = _cache.GetResource<RSIResource>(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)
{ {
if (!TryComp(args.Item, out ClothingComponent? clothing) || clothing.InSlot == null) if (!TryComp(args.Item, out ClothingComponent? clothing) || clothing.InSlot == null)
return; return;
@@ -153,19 +67,7 @@ public class ClothingSystem : EntitySystem
private void OnDidUnequip(EntityUid uid, SpriteComponent component, DidUnequipEvent args) private void OnDidUnequip(EntityUid uid, SpriteComponent component, DidUnequipEvent args)
{ {
if (!TryComp(uid, out ClientInventoryComponent? inventory) || !TryComp(uid, out SpriteComponent? sprite)) component.LayerSetVisible(args.Slot, false);
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();
} }
public void InitClothing(EntityUid uid, ClientInventoryComponent? component = null, SpriteComponent? sprite = null) public void InitClothing(EntityUid uid, ClientInventoryComponent? component = null, SpriteComponent? sprite = null)
@@ -174,6 +76,8 @@ public class ClothingSystem : EntitySystem
foreach (var slot in slots) foreach (var slot in slots)
{ {
sprite.LayerMapReserveBlank(slot.Name);
if (!_inventorySystem.TryGetSlotContainer(uid, slot.Name, out var containerSlot, out _, component) || if (!_inventorySystem.TryGetSlotContainer(uid, slot.Name, out var containerSlot, out _, component) ||
!containerSlot.ContainedEntity.HasValue) continue; !containerSlot.ContainedEntity.HasValue) continue;
@@ -185,15 +89,55 @@ public class ClothingSystem : EntitySystem
{ {
component.InSlot = args.Slot; component.InSlot = args.Slot;
RenderEquipment(args.Equipee, uid, args.Slot, clothingComponent: component); if (!TryComp<SpriteComponent>(args.Equipee, out var sprite) || !TryComp<ClientInventoryComponent>(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, private void RenderEquipment(EntityUid uid, EntityUid equipment, string slot,
ClientInventoryComponent? inventory = null, SpriteComponent? sprite = null, ClothingComponent? clothingComponent = null) 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; 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 _)) if (slot == "jumpsuit" && sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out _))
{ {
sprite.LayerSetState(HumanoidVisualLayers.StencilMask, clothingComponent.FemaleMask switch sprite.LayerSetState(HumanoidVisualLayers.StencilMask, clothingComponent.FemaleMask switch
@@ -203,55 +147,34 @@ public class ClothingSystem : EntitySystem
_ => "female_full", _ => "female_full",
}); });
} }
}
// Remove old layers. We could also just set them to invisible, but as items may add arbitrary layers, this public (RSI rsi, RSI.StateId stateId)? GetEquippedStateInfo(EntityUid uid, string slot, string? speciesId=null, ClothingComponent? component = null)
// may eventually bloat the player with lots of invisible layers. {
if (inventory.VisualLayerKeys.TryGetValue(slot, out var revealedLayers)) if (!Resolve(uid, ref component))
return null;
if (component.RsiPath == null)
return null;
var rsi = _cache.GetResource<RSIResource>(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(); return (rsi, stateId);
inventory.VisualLayerKeys[slot] = revealedLayers;
} }
var ev = new GetEquipmentVisualsEvent(equipee, slot); return null;
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));
} }
} }

View File

@@ -1,7 +1,7 @@
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Robust.Shared.Analyzers; using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using System.Collections.Generic; using Robust.Shared.IoC;
namespace Content.Client.Hands namespace Content.Client.Hands
{ {
@@ -11,10 +11,5 @@ namespace Content.Client.Hands
public class HandsComponent : SharedHandsComponent public class HandsComponent : SharedHandsComponent
{ {
public HandsGui? Gui { get; set; } public HandsGui? Gui { get; set; }
/// <summary>
/// Data about the current sprite layers that the hand is contributing to the owner entity. Used for sprite in-hands.
/// </summary>
public readonly Dictionary<HandLocation, HashSet<string>> RevealedLayers = new();
} }
} }

View File

@@ -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<IEntityManager>();
if (!entities.TryGetComponent<ISpriteComponent>(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<IResourceCache>();
var hands = visualState.Hands;
foreach (var hand in hands)
{
var rsi = resourceCache.GetResource<RSIResource>(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();
}
}
}

View File

@@ -4,7 +4,6 @@ using Content.Client.Animations;
using Content.Client.HUD; using Content.Client.HUD;
using Content.Shared.Hands; using Content.Shared.Hands;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Item;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Player; using Robust.Client.Player;
@@ -12,7 +11,6 @@ using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -34,7 +32,6 @@ namespace Content.Client.Hands
SubscribeLocalEvent<HandsComponent, PlayerDetachedEvent>(HandlePlayerDetached); SubscribeLocalEvent<HandsComponent, PlayerDetachedEvent>(HandlePlayerDetached);
SubscribeLocalEvent<HandsComponent, ComponentRemove>(HandleCompRemove); SubscribeLocalEvent<HandsComponent, ComponentRemove>(HandleCompRemove);
SubscribeLocalEvent<HandsComponent, ComponentHandleState>(HandleComponentState); SubscribeLocalEvent<HandsComponent, ComponentHandleState>(HandleComponentState);
SubscribeLocalEvent<HandsComponent, VisualsChangedEvent>(OnVisualsChanged);
SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation); SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
} }
@@ -183,95 +180,6 @@ namespace Content.Client.Hands
RaiseNetworkEvent(new ActivateInHandMsg(handName)); 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);
}
}
/// <summary>
/// Update the players sprite with new in-hand visuals.
/// </summary>
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 #region Gui
public void UpdateGui(HandsComponent? hands = null) public void UpdateGui(HandsComponent? hands = null)
{ {
@@ -285,6 +193,14 @@ namespace Content.Client.Hands
hands.Gui.Update(new HandsGuiState(states, hands.ActiveHand)); 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) public override bool TrySetActiveHand(EntityUid uid, string? value, SharedHandsComponent? handComp = null)
{ {
if (!base.TrySetActiveHand(uid, value, handComp)) if (!base.TrySetActiveHand(uid, value, handComp))

View File

@@ -30,12 +30,6 @@ namespace Content.Client.Inventory
[ViewVariables] [ViewVariables]
[DataField("speciesId")] public string? SpeciesId { get; set; } [DataField("speciesId")] public string? SpeciesId { get; set; }
/// <summary>
/// Data about the current layers that have been added to the players sprite due to the items in each equipment slot.
/// </summary>
[ViewVariables]
public readonly Dictionary<string, HashSet<string>> VisualLayerKeys = new();
public bool AttachedToGameHud; public bool AttachedToGameHud;
} }
} }

View File

@@ -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<SharedItemComponent, GetInhandVisualsEvent>(OnGetVisuals);
}
#region InhandVisuals
/// <summary>
/// When an items visual state changes, notify and entities that are holding this item that their sprite may need updating.
/// </summary>
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));
}
/// <summary>
/// An entity holding this item is requesting visual information for in-hand sprites.
/// </summary>
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));
}
}
/// <summary>
/// If no explicit in-hand visuals were specified, this attempts to populate with default values.
/// </summary>
/// <remarks>
/// Useful for lazily adding in-hand sprites without modifying yaml. And backwards compatibility.
/// </remarks>
private bool TryGetDefaultVisuals(EntityUid uid, SharedItemComponent item, string defaultKey, [NotNullWhen(true)] out List<PrototypeLayerData>? result)
{
result = null;
RSI? rsi = null;
if (item.RsiPath != null)
rsi = _resCache.GetResource<RSIResource>(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
}

View File

@@ -1,8 +0,0 @@
using Content.Shared.Item;
namespace Content.Server.Item;
public sealed class ItemSystem : SharedItemSystem
{
// Ello Guvna
}

View File

@@ -1,60 +0,0 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using static Robust.Shared.GameObjects.SharedSpriteComponent;
namespace Content.Shared.Clothing;
/// <summary>
/// Raised directed at a piece of clothing to get the set of layers to show on the wearer's sprite
/// </summary>
public class GetEquipmentVisualsEvent : EntityEventArgs
{
/// <summary>
/// Entity that is wearing the item.
/// </summary>
public readonly EntityUid Equipee;
public readonly string Slot;
/// <summary>
/// The layers that will be added to the entity that is wearing this item.
/// </summary>
/// <remarks>
/// Note that the actual ordering of the layers depends on the order in which they are added to this list;
/// </remarks>
public List<(string, PrototypeLayerData)> Layers = new();
public GetEquipmentVisualsEvent(EntityUid equipee, string slot)
{
Equipee = equipee;
Slot = slot;
}
}
/// <summary>
/// Raised directed at a piece of clothing after its visuals have been updated.
/// </summary>
/// <remarks>
/// Useful for systems/components that modify the visual layers that an item adds to a player. (e.g. RGB memes)
/// </remarks>
public class EquipmentVisualsUpdatedEvent : EntityEventArgs
{
/// <summary>
/// Entity that is wearing the item.
/// </summary>
public readonly EntityUid Equipee;
public readonly string Slot;
/// <summary>
/// The layers that this item is now revealing.
/// </summary>
public HashSet<string> RevealedLayers;
public EquipmentVisualsUpdatedEvent(EntityUid equipee, string slot, HashSet<string> revealedLayers)
{
Equipee = equipee;
Slot = slot;
RevealedLayers = revealedLayers;
}
}

View File

@@ -1,5 +1,4 @@
using System; using System;
using Content.Shared.Item;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
@@ -41,7 +40,7 @@ namespace Content.Shared.Hands.Components
// update hands GUI with new entity. // update hands GUI with new entity.
if (Owner.TryGetContainer(out var containter)) if (Owner.TryGetContainer(out var containter))
EntitySystem.Get<SharedItemSystem>().VisualsChanged(Owner); EntitySystem.Get<SharedHandsSystem>().UpdateHandVisuals(containter.Owner);
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]

View File

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Item; using Content.Shared.Item;
using Robust.Shared.Containers; using Robust.Shared.Containers;
@@ -16,7 +18,6 @@ using Robust.Shared.Physics;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
using static Robust.Shared.GameObjects.SharedSpriteComponent;
namespace Content.Shared.Hands.Components 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<HandVisualState> Hands { get; } = new();
public HandsVisualState(List<HandVisualState> hands)
{
Hands = hands;
}
public object Clone()
{
return new HandsVisualState(new List<HandVisualState>(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] [Serializable, NetSerializable]
public class Hand public class Hand
{ {

View File

@@ -1,67 +1,13 @@
using System; using System;
using System.Collections.Generic;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using static Robust.Shared.GameObjects.SharedSpriteComponent;
namespace Content.Shared.Hands namespace Content.Shared.Hands
{ {
/// <summary>
/// Raised directed at an item that needs to update its in-hand sprites/layers.
/// </summary>
public class GetInhandVisualsEvent : EntityEventArgs
{
/// <summary>
/// Entity that owns the hand holding the item.
/// </summary>
public readonly EntityUid User;
public readonly HandLocation Location;
/// <summary>
/// The layers that will be added to the entity that is holding this item.
/// </summary>
/// <remarks>
/// Note that the actual ordering of the layers depends on the order in which they are added to this list;
/// </remarks>
public List<(string, PrototypeLayerData)> Layers = new();
public GetInhandVisualsEvent(EntityUid user, HandLocation location)
{
User = user;
Location = location;
}
}
/// <summary>
/// Raised directed at an item after its visuals have been updated.
/// </summary>
/// <remarks>
/// Useful for systems/components that modify the visual layers that an item adds to a player. (e.g. RGB memes)
/// </remarks>
public class HeldVisualsUpdatedEvent : EntityEventArgs
{
/// <summary>
/// Entity that is holding the item.
/// </summary>
public readonly EntityUid User;
/// <summary>
/// The layers that this item is now revealing.
/// </summary>
public HashSet<string> RevealedLayers;
public HeldVisualsUpdatedEvent(EntityUid user, HashSet<string> revealedLayers)
{
User = user;
RevealedLayers = revealedLayers;
}
}
/// <summary> /// <summary>
/// Raised when an entity item in a hand is deselected. /// Raised when an entity item in a hand is deselected.
/// </summary> /// </summary>

View File

@@ -2,6 +2,7 @@ using Content.Shared.Administration.Logs;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Input; using Content.Shared.Input;
using Content.Shared.Item;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding; using Robust.Shared.Input.Binding;
@@ -10,6 +11,7 @@ using Robust.Shared.Log;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Players; using Robust.Shared.Players;
using System.Collections.Generic;
namespace Content.Shared.Hands namespace Content.Shared.Hands
{ {
@@ -23,7 +25,8 @@ namespace Content.Shared.Hands
SubscribeAllEvent<RequestSetHandEvent>(HandleSetHand); SubscribeAllEvent<RequestSetHandEvent>(HandleSetHand);
SubscribeLocalEvent<SharedHandsComponent, EntRemovedFromContainerMessage>(HandleContainerRemoved); SubscribeLocalEvent<SharedHandsComponent, EntRemovedFromContainerMessage>(HandleContainerRemoved);
SubscribeLocalEvent<SharedHandsComponent, EntInsertedIntoContainerMessage>(HandleContainerInserted); SubscribeLocalEvent<SharedHandsComponent, EntInsertedIntoContainerMessage>(HandleContainerModified);
SubscribeLocalEvent<SharedHandsComponent, ItemPrefixChangeEvent>(OnPrefixChanged);
CommandBinds.Builder CommandBinds.Builder
.Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed)) .Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed))
@@ -31,6 +34,15 @@ namespace Content.Shared.Hands
.Register<SharedHandsSystem>(); .Register<SharedHandsSystem>();
} }
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() public override void Shutdown()
{ {
base.Shutdown(); base.Shutdown();
@@ -124,25 +136,43 @@ namespace Content.Shared.Hands
public abstract void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, public abstract void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition,
EntityUid? exclude); EntityUid? exclude);
#endregion
protected virtual void HandleContainerRemoved(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args) protected virtual void HandleContainerRemoved(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args)
{ {
HandleContainerModified(uid, component, 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) /// <summary>
/// Update the In-Hand sprites
/// </summary>
public virtual void UpdateHandVisuals(EntityUid uid, SharedHandsComponent? handComp = null, AppearanceComponent? appearance = null)
{ {
// un-rotate entities. needed for things like directional flashlights if (!Resolve(uid, ref handComp, ref appearance, false))
Transform(args.Entity).LocalRotation = 0; return;
HandleContainerModified(uid, component, args); var handsVisuals = new List<HandVisualState>();
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) private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs)
{ {

View File

@@ -53,9 +53,6 @@ public abstract partial class InventorySystem
if(!TryGetSlot(uid, args.Container.ID, out var slotDef, inventory: component)) if(!TryGetSlot(uid, args.Container.ID, out var slotDef, inventory: component))
return; 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); var equippedEvent = new DidEquipEvent(uid, args.Entity, slotDef);
RaiseLocalEvent(uid, equippedEvent); RaiseLocalEvent(uid, equippedEvent);

View File

@@ -4,11 +4,10 @@ using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using System;
namespace Content.Shared.Item namespace Content.Shared.Item
{ {
public abstract class SharedItemSystem : EntitySystem public class ItemSystem : EntitySystem
{ {
public override void Initialize() public override void Initialize()
{ {
@@ -29,11 +28,13 @@ namespace Content.Shared.Item
component.Size = state.Size; component.Size = state.Size;
component.EquippedPrefix = state.EquippedPrefix; component.EquippedPrefix = state.EquippedPrefix;
component.Color = state.Color;
component.RsiPath = state.RsiPath;
} }
private void OnGetState(EntityUid uid, SharedItemComponent component, ref ComponentGetState args) 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) private void OnUnequipped(EntityUid uid, SharedSpriteComponent component, GotUnequippedEvent args)
@@ -69,11 +70,5 @@ namespace Content.Shared.Item
args.Verbs.Add(verb); args.Verbs.Add(verb);
} }
/// <summary>
/// Notifies any entity that is holding or wearing this item that they may need to update their sprite.
/// </summary>
public virtual void VisualsChanged(EntityUid owner, SharedItemComponent? item = null)
{ }
} }
} }

View File

@@ -1,10 +1,11 @@
using System; using System;
using System.Collections.Generic; using Content.Shared.Hands;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers; using Content.Shared.Interaction.Helpers;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Sound; using Content.Shared.Sound;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -12,7 +13,6 @@ using Robust.Shared.Maths;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
using static Robust.Shared.GameObjects.SharedSpriteComponent;
namespace Content.Shared.Item namespace Content.Shared.Item
{ {
@@ -40,18 +40,10 @@ namespace Content.Shared.Item
[DataField("size")] [DataField("size")]
private int _size; private int _size;
[DataField("inhandVisuals")]
public Dictionary<HandLocation, List<PrototypeLayerData>> InhandVisuals = new();
[DataField("clothingVisuals")]
public Dictionary<string, List<PrototypeLayerData>> ClothingVisuals = new();
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <remarks> // todo paul make this update slotvisuals on client on change
/// Only used if <see cref="InhandVisuals"/> or <see cref="ClothingVisuals"/> are unspecified.
/// </remarks>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public string? EquippedPrefix public string? EquippedPrefix
{ {
@@ -59,7 +51,7 @@ namespace Content.Shared.Item
set set
{ {
_equippedPrefix = value; _equippedPrefix = value;
EntitySystem.Get<SharedItemSystem>().VisualsChanged(Owner, this); OnEquippedPrefixChange();
Dirty(); Dirty();
} }
} }
@@ -73,7 +65,6 @@ namespace Content.Shared.Item
[DataField("EquipSound")] [DataField("EquipSound")]
public SoundSpecifier? EquipSound { get; set; } = default!; public SoundSpecifier? EquipSound { get; set; } = default!;
// TODO REMOVE. Currently nonfunctional and only used by RGB system. #6253 Fixes this but requires #6252
/// <summary> /// <summary>
/// Color of the sprite shown on the player when this item is in their hands. /// Color of the sprite shown on the player when this item is in their hands.
/// </summary> /// </summary>
@@ -91,11 +82,20 @@ namespace Content.Shared.Item
private Color _color = Color.White; private Color _color = Color.White;
/// <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.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public string? RsiPath
{
get => _rsiPath;
set
{
_rsiPath = value;
Dirty();
}
}
[DataField("sprite")] [DataField("sprite")]
public readonly string? RsiPath; private string? _rsiPath;
bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs)
{ {
@@ -116,6 +116,12 @@ namespace Content.Shared.Item
return hands.TryPickupEntityToActiveHand(Owner, animateUser: true); 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() public void RemovedFromSlot()
{ {
if (_entMan.TryGetComponent(Owner, out SharedSpriteComponent component)) if (_entMan.TryGetComponent(Owner, out SharedSpriteComponent component))
@@ -134,25 +140,29 @@ namespace Content.Shared.Item
{ {
public int Size { get; } public int Size { get; }
public string? EquippedPrefix { 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; Size = size;
EquippedPrefix = equippedPrefix; EquippedPrefix = equippedPrefix;
Color = color;
RsiPath = rsiPath;
} }
} }
/// <summary> /// <summary>
/// Raised when an item's visual state is changed. The event is directed at the entity that contains this item, so /// 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 hands or inventory sprites and GUI. /// that it can properly update its sprite/GUI.
/// </summary> /// </summary>
[Serializable, NetSerializable] [Serializable, NetSerializable]
public class VisualsChangedEvent : EntityEventArgs public class ItemPrefixChangeEvent : EntityEventArgs
{ {
public readonly EntityUid Item; public readonly EntityUid Item;
public readonly string ContainerId; public readonly string ContainerId;
public VisualsChangedEvent(EntityUid item, string containerId) public ItemPrefixChangeEvent(EntityUid item, string containerId)
{ {
Item = item; Item = item;
ContainerId = containerId; ContainerId = containerId;

View File

@@ -502,6 +502,7 @@
normal: monkey normal: monkey
crit: dead crit: dead
dead: dead dead: dead
- type: HandsVisualizer
- type: FireVisualizer - type: FireVisualizer
sprite: Mobs/Effects/onfire.rsi sprite: Mobs/Effects/onfire.rsi
normalState: Monkey_burning normalState: Monkey_burning

View File

@@ -243,6 +243,7 @@
fireStackAlternateState: 3 fireStackAlternateState: 3
- type: CreamPiedVisualizer - type: CreamPiedVisualizer
state: creampie_human state: creampie_human
- type: HandsVisualizer
- type: DamageVisualizer - type: DamageVisualizer
thresholds: [20, 40, 100] thresholds: [20, 40, 100]
targetLayers: targetLayers: