Clean up vending machines and port their visualizer (#10465)

This commit is contained in:
Andreas Kämper
2022-08-31 14:12:09 +02:00
committed by GitHub
parent 6b0e03e0d7
commit 42f3155c85
62 changed files with 873 additions and 850 deletions

View File

@@ -1,62 +1,87 @@
using System;
using System.Collections.Generic;
using Content.Shared.VendingMachines;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using static Content.Shared.VendingMachines.SharedVendingMachineComponent;
namespace Content.Client.VendingMachines.UI
{
[GenerateTypedNameReferences]
public sealed partial class VendingMachineMenu : DefaultWindow
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private VendingMachineBoundUserInterface Owner { get; }
public event Action<ItemList.ItemListSelectedEventArgs>? OnItemSelected;
private List<VendingMachineInventoryEntry> _cachedInventory = new();
public VendingMachineMenu(VendingMachineBoundUserInterface owner)
public VendingMachineMenu()
{
IoCManager.InjectDependencies(this);
MinSize = SetSize = (250, 150);
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Owner = owner;
VendingContents.OnItemSelected += ItemSelected;
VendingContents.OnItemSelected += args =>
{
OnItemSelected?.Invoke(args);
};
}
/// <summary>
/// Populates the list of available items on the vending machine interface
/// and sets icons based on their prototypes
/// </summary>
public void Populate(List<VendingMachineInventoryEntry> inventory)
{
VendingContents.Clear();
_cachedInventory = inventory;
var longestEntry = "";
foreach (VendingMachineInventoryEntry entry in inventory)
if (inventory.Count == 0)
{
var itemName = _prototypeManager.Index<EntityPrototype>(entry.ID).Name;
VendingContents.Clear();
var outOfStockText = Loc.GetString("vending-machine-component-try-eject-out-of-stock");
VendingContents.AddItem(outOfStockText);
SetSizeAfterUpdate(outOfStockText.Length);
return;
}
while (inventory.Count != VendingContents.Count)
{
if (inventory.Count > VendingContents.Count)
VendingContents.AddItem(string.Empty);
else
VendingContents.RemoveAt(VendingContents.Count - 1);
}
var longestEntry = string.Empty;
var spriteSystem = EntitySystem.Get<SpriteSystem>();
for (var i = 0; i < inventory.Count; i++)
{
var entry = inventory[i];
var vendingItem = VendingContents[i];
vendingItem.Text = string.Empty;
vendingItem.Icon = null;
var itemName = entry.ID;
Texture? icon = null;
if (_prototypeManager.TryIndex<EntityPrototype>(entry.ID, out var prototype))
{
itemName = prototype.Name;
icon = spriteSystem.GetPrototypeIcon(prototype).Default;
}
if (itemName.Length > longestEntry.Length)
longestEntry = itemName;
Texture? icon = null;
if(_prototypeManager.TryIndex(entry.ID, out EntityPrototype? prototype))
icon = SpriteComponent.GetPrototypeIcon(prototype, _resourceCache).Default;
VendingContents.AddItem($"{itemName} [{entry.Amount}]", icon);
vendingItem.Text = $"{itemName} [{entry.Amount}]";
vendingItem.Icon = icon;
}
SetSize = (Math.Clamp((longestEntry.Length + 2) * 12, 250, 300),
Math.Clamp(VendingContents.Count * 50, 150, 350));
SetSizeAfterUpdate(longestEntry.Length);
}
public void ItemSelected(ItemList.ItemListSelectedEventArgs args)
private void SetSizeAfterUpdate(int longestEntryLength)
{
Owner.Eject(_cachedInventory[args.ItemIndex].Type, _cachedInventory[args.ItemIndex].ID);
SetSize = (Math.Clamp((longestEntryLength + 2) * 12, 250, 300),
Math.Clamp(VendingContents.Count * 50, 150, 350));
}
}
}

View File

@@ -1,241 +0,0 @@
using System;
using System.Collections.Generic;
using Content.Shared.VendingMachines;
using JetBrains.Annotations;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using static Content.Shared.VendingMachines.SharedVendingMachineComponent;
namespace Content.Client.VendingMachines.UI
{
[UsedImplicitly]
public sealed class VendingMachineVisualizer : AppearanceVisualizer, ISerializationHooks
{
// TODO: Should default to off or broken if damaged
//
// TODO: The length of these animations is supposed to be dictated
// by the vending machine's pack prototype's `AnimationDuration`
// but we have no good way of passing that data from the server
// to the client at the moment. Rework Visualizers?
private Dictionary<string, bool> _baseStates = new();
private static readonly Dictionary<string, VendingMachineVisualLayers> LayerMap =
new()
{
{"off", VendingMachineVisualLayers.Unlit},
{"screen", VendingMachineVisualLayers.Screen},
{"normal", VendingMachineVisualLayers.Base},
{"normal-unshaded", VendingMachineVisualLayers.BaseUnshaded},
{"eject", VendingMachineVisualLayers.Base},
{"eject-unshaded", VendingMachineVisualLayers.BaseUnshaded},
{"deny", VendingMachineVisualLayers.Base},
{"deny-unshaded", VendingMachineVisualLayers.BaseUnshaded},
{"broken", VendingMachineVisualLayers.Unlit},
};
[DataField("screen")]
private bool _screen;
[DataField("normal")]
private bool _normal;
[DataField("normalUnshaded")]
private bool _normalUnshaded;
[DataField("eject")]
private bool _eject;
[DataField("ejectUnshaded")]
private bool _ejectUnshaded;
[DataField("deny")]
private bool _deny;
[DataField("denyUnshaded")]
private bool _denyUnshaded;
[DataField("broken")]
private bool _broken;
[DataField("brokenUnshaded")]
private bool _brokenUnshaded;
private readonly Dictionary<string, Animation> _animations = new();
void ISerializationHooks.AfterDeserialization()
{
// Used a dictionary so the yaml can adhere to the style-guide and the texture states can be clear
var states = new Dictionary<string, bool>
{
{"off", true},
{"screen", _screen},
{"normal", _normal},
{"normal-unshaded", _normalUnshaded},
{"eject", _eject},
{"eject-unshaded", _ejectUnshaded},
{"deny", _deny},
{"deny-unshaded", _denyUnshaded},
{"broken", _broken},
{"broken-unshaded", _brokenUnshaded},
};
_baseStates = states;
if (_baseStates["deny"])
{
InitializeAnimation("deny");
}
if (_baseStates["deny-unshaded"])
{
InitializeAnimation("deny-unshaded", true);
}
if (_baseStates["eject"])
{
InitializeAnimation("eject");
}
if (_baseStates["eject-unshaded"])
{
InitializeAnimation("eject-unshaded", true);
}
}
private void InitializeAnimation(string key, bool unshaded = false)
{
_animations.Add(key, new Animation {Length = TimeSpan.FromSeconds(1.2f)});
var flick = new AnimationTrackSpriteFlick();
_animations[key].AnimationTracks.Add(flick);
flick.LayerKey = unshaded ? VendingMachineVisualLayers.BaseUnshaded : VendingMachineVisualLayers.Base;
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame(key, 0f));
}
[Obsolete("Subscribe to your component being initialised instead.")]
public override void InitializeEntity(EntityUid entity)
{
base.InitializeEntity(entity);
IoCManager.Resolve<IEntityManager>().EnsureComponent<AnimationPlayerComponent>(entity);
}
private void HideLayers(ISpriteComponent spriteComponent)
{
foreach (var layer in spriteComponent.AllLayers)
{
layer.Visible = false;
}
spriteComponent.LayerSetVisible(VendingMachineVisualLayers.Unlit, true);
}
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var entMan = IoCManager.Resolve<IEntityManager>();
var sprite = entMan.GetComponent<SpriteComponent>(component.Owner);
// TODO when moving to a system visualizer, re work how this is done
// Currently this only gets called during init, so unless it NEEEDS to be configurable, just make this party of the entity prototype.
if (component.TryGetData(VendingMachineVisuals.Inventory, out string? invId) &&
IoCManager.Resolve<IPrototypeManager>().TryIndex(invId, out VendingMachineInventoryPrototype? prototype) &&
IoCManager.Resolve<IResourceCache>().TryGetResource<RSIResource>(
SharedSpriteComponent.TextureRoot / $"Structures/Machines/VendingMachines/{prototype.SpriteName}.rsi", out var res))
{
sprite.BaseRSI = res.RSI;
}
var animPlayer = entMan.GetComponent<AnimationPlayerComponent>(component.Owner);
if (!component.TryGetData(VendingMachineVisuals.VisualState, out VendingMachineVisualState state))
{
state = VendingMachineVisualState.Normal;
}
// Hide last state
HideLayers(sprite);
ActivateState(sprite, "off");
switch (state)
{
case VendingMachineVisualState.Normal:
ActivateState(sprite, "screen");
ActivateState(sprite, "normal-unshaded");
ActivateState(sprite, "normal");
break;
case VendingMachineVisualState.Off:
break;
case VendingMachineVisualState.Broken:
ActivateState(sprite, "broken-unshaded");
ActivateState(sprite, "broken");
break;
case VendingMachineVisualState.Deny:
ActivateState(sprite, "screen");
ActivateAnimation(sprite, animPlayer, "deny-unshaded");
ActivateAnimation(sprite, animPlayer, "deny");
break;
case VendingMachineVisualState.Eject:
ActivateState(sprite, "screen");
ActivateAnimation(sprite, animPlayer, "eject-unshaded");
ActivateAnimation(sprite, animPlayer, "eject");
break;
default:
throw new ArgumentOutOfRangeException();
}
}
// Helper methods just to avoid all of that hard-to-read-indented code
private void ActivateState(ISpriteComponent spriteComponent, string stateId)
{
// No state for it on the rsi :(
if (!_baseStates[stateId])
{
return;
}
var stateLayer = LayerMap[stateId];
spriteComponent.LayerSetVisible(stateLayer, true);
spriteComponent.LayerSetState(stateLayer, stateId);
}
private void ActivateAnimation(ISpriteComponent spriteComponent, AnimationPlayerComponent animationPlayer, string key)
{
if (!_animations.TryGetValue(key, out var animation))
{
return;
}
if (!animationPlayer.HasRunningAnimation(key))
{
spriteComponent.LayerSetVisible(LayerMap[key], true);
animationPlayer.Play(animation, key);
}
}
public enum VendingMachineVisualLayers : byte
{
// Off / Broken. The other layers will overlay this if the machine is on.
Unlit,
// Normal / Deny / Eject
Base,
BaseUnshaded,
// Screens that are persistent (where the machine is not off or broken)
Screen,
}
}
}