Add multi-layer in-hand and clothing support (#6740)

This commit is contained in:
Leon Friedrich
2022-03-02 12:29:42 +13:00
committed by GitHub
parent d088f0f02c
commit c4c238bda9
17 changed files with 529 additions and 262 deletions

View File

@@ -1,7 +1,7 @@
using Content.Shared.Hands.Components;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using System.Collections.Generic;
namespace Content.Client.Hands
{
@@ -11,5 +11,10 @@ namespace Content.Client.Hands
public sealed class HandsComponent : SharedHandsComponent
{
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

@@ -1,62 +0,0 @@
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 sealed 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,6 +4,7 @@ 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;
@@ -11,6 +12,7 @@ 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;
@@ -32,6 +34,7 @@ namespace Content.Client.Hands
SubscribeLocalEvent<HandsComponent, PlayerDetachedEvent>(HandlePlayerDetached);
SubscribeLocalEvent<HandsComponent, ComponentRemove>(HandleCompRemove);
SubscribeLocalEvent<HandsComponent, ComponentHandleState>(HandleComponentState);
SubscribeLocalEvent<HandsComponent, VisualsChangedEvent>(OnVisualsChanged);
SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
}
@@ -180,6 +183,95 @@ 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);
}
}
/// <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
public void UpdateGui(HandsComponent? hands = null)
{
@@ -193,14 +285,6 @@ 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))