Clean up vending machines and port their visualizer (#10465)
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,20 @@
|
||||
using Content.Client.VendingMachines.UI;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Content.Shared.VendingMachines.SharedVendingMachineComponent;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.VendingMachines
|
||||
{
|
||||
public sealed class VendingMachineBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables] private VendingMachineMenu? _menu;
|
||||
[ViewVariables]
|
||||
private VendingMachineMenu? _menu;
|
||||
|
||||
public SharedVendingMachineComponent? VendingMachine { get; private set; }
|
||||
private List<VendingMachineInventoryEntry> _cachedInventory = new();
|
||||
|
||||
public VendingMachineBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
SendMessage(new InventorySyncRequestMessage());
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
@@ -24,33 +22,43 @@ namespace Content.Client.VendingMachines
|
||||
base.Open();
|
||||
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entMan.TryGetComponent(Owner.Owner, out SharedVendingMachineComponent? vendingMachine))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var vendingMachineSys = EntitySystem.Get<VendingMachineSystem>();
|
||||
|
||||
VendingMachine = vendingMachine;
|
||||
_cachedInventory = vendingMachineSys.GetAllInventory(Owner.Owner);
|
||||
|
||||
_menu = new VendingMachineMenu(this) {Title = entMan.GetComponent<MetaDataComponent>(Owner.Owner).EntityName};
|
||||
_menu.Populate(VendingMachine.AllInventory);
|
||||
_menu = new VendingMachineMenu {Title = entMan.GetComponent<MetaDataComponent>(Owner.Owner).EntityName};
|
||||
|
||||
_menu.OnClose += Close;
|
||||
_menu.OnItemSelected += OnItemSelected;
|
||||
|
||||
_menu.Populate(_cachedInventory);
|
||||
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
public void Eject(InventoryType type, string id)
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
SendMessage(new VendingMachineEjectMessage(type, id));
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not VendingMachineInterfaceState newState)
|
||||
return;
|
||||
|
||||
_cachedInventory = newState.Inventory;
|
||||
|
||||
_menu?.Populate(_cachedInventory);
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
private void OnItemSelected(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case VendingMachineInventoryMessage msg:
|
||||
_menu?.Populate(msg.Inventory);
|
||||
break;
|
||||
}
|
||||
if (_cachedInventory == null || _cachedInventory.Count == 0)
|
||||
return;
|
||||
|
||||
var selectedItem = _cachedInventory.ElementAtOrDefault(args.ItemIndex);
|
||||
|
||||
if (selectedItem == null)
|
||||
return;
|
||||
|
||||
SendMessage(new VendingMachineEjectMessage(selectedItem.Type, selectedItem.ID));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -59,7 +67,12 @@ namespace Content.Client.VendingMachines
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_menu?.Dispose();
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
_menu.OnItemSelected -= OnItemSelected;
|
||||
_menu.OnClose -= Close;
|
||||
_menu.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,57 @@ namespace Content.Client.VendingMachines;
|
||||
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedVendingMachineComponent))]
|
||||
[Access(typeof(VendingMachineSystem))]
|
||||
public sealed class VendingMachineComponent : SharedVendingMachineComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// RSI state for when the vending machine is unpowered.
|
||||
/// Will be displayed on the layer <see cref="VendingMachineVisualLayers.Base"/>
|
||||
/// </summary>
|
||||
[DataField("offState")]
|
||||
public string? OffState;
|
||||
|
||||
/// <summary>
|
||||
/// RSI state for the screen of the vending machine
|
||||
/// Will be displayed on the layer <see cref="VendingMachineVisualLayers.Screen"/>
|
||||
/// </summary>
|
||||
[DataField("screenState")]
|
||||
public string? ScreenState;
|
||||
|
||||
/// <summary>
|
||||
/// RSI state for the vending machine's normal state. Usually a looping animation.
|
||||
/// Will be displayed on the layer <see cref="VendingMachineVisualLayers.BaseUnshaded"/>
|
||||
/// </summary>
|
||||
[DataField("normalState")]
|
||||
public string? NormalState;
|
||||
|
||||
/// <summary>
|
||||
/// RSI state for the vending machine's eject animation.
|
||||
/// Will be displayed on the layer <see cref="VendingMachineVisualLayers.BaseUnshaded"/>
|
||||
/// </summary>
|
||||
[DataField("ejectState")]
|
||||
public string? EjectState;
|
||||
|
||||
/// <summary>
|
||||
/// RSI state for the vending machine's deny animation. Will either be played once as sprite flick
|
||||
/// or looped depending on how <see cref="LoopDenyAnimation"/> is set.
|
||||
/// Will be displayed on the layer <see cref="VendingMachineVisualLayers.BaseUnshaded"/>
|
||||
/// </summary>
|
||||
[DataField("denyState")]
|
||||
public string? DenyState;
|
||||
|
||||
/// <summary>
|
||||
/// RSI state for when the vending machine is unpowered.
|
||||
/// Will be displayed on the layer <see cref="VendingMachineVisualLayers.Base"/>
|
||||
/// </summary>
|
||||
[DataField("brokenState")]
|
||||
public string? BrokenState;
|
||||
|
||||
/// <summary>
|
||||
/// If set to <c>true</c> (default) will loop the animation of the <see cref="DenyState"/> for the duration
|
||||
/// of <see cref="SharedVendingMachineComponent.DenyDelay"/>. If set to <c>false</c> will play a sprite
|
||||
/// flick animation for the state and then linger on the final frame until the end of the delay.
|
||||
/// </summary>
|
||||
[DataField("loopDeny")]
|
||||
public bool LoopDenyAnimation = true;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,148 @@
|
||||
using Content.Shared.VendingMachines;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.VendingMachines;
|
||||
|
||||
public sealed class VendingMachineSystem : SharedVendingMachineSystem
|
||||
{
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<VendingMachineComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
SubscribeLocalEvent<VendingMachineComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(EntityUid uid, VendingMachineComponent component, AnimationCompletedEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
UpdateAppearance(uid, VendingMachineVisualState.Normal, component, sprite);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, VendingMachineComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (!args.AppearanceData.TryGetValue(VendingMachineVisuals.VisualState, out var visualStateObject) ||
|
||||
visualStateObject is not VendingMachineVisualState visualState)
|
||||
{
|
||||
visualState = VendingMachineVisualState.Normal;
|
||||
}
|
||||
|
||||
UpdateAppearance(uid, visualState, component, args.Sprite);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, VendingMachineVisualState visualState, VendingMachineComponent component, SpriteComponent sprite)
|
||||
{
|
||||
SetLayerState(VendingMachineVisualLayers.Base, component.OffState, sprite);
|
||||
|
||||
switch (visualState)
|
||||
{
|
||||
case VendingMachineVisualState.Normal:
|
||||
SetLayerState(VendingMachineVisualLayers.BaseUnshaded, component.NormalState, sprite);
|
||||
SetLayerState(VendingMachineVisualLayers.Screen, component.ScreenState, sprite);
|
||||
break;
|
||||
|
||||
case VendingMachineVisualState.Deny:
|
||||
if (component.LoopDenyAnimation)
|
||||
SetLayerState(VendingMachineVisualLayers.BaseUnshaded, component.DenyState, sprite);
|
||||
else
|
||||
PlayAnimation(uid, VendingMachineVisualLayers.BaseUnshaded, component.DenyState, component.DenyDelay, sprite);
|
||||
|
||||
SetLayerState(VendingMachineVisualLayers.Screen, component.ScreenState, sprite);
|
||||
break;
|
||||
|
||||
case VendingMachineVisualState.Eject:
|
||||
PlayAnimation(uid, VendingMachineVisualLayers.BaseUnshaded, component.EjectState, component.EjectDelay, sprite);
|
||||
SetLayerState(VendingMachineVisualLayers.Screen, component.ScreenState, sprite);
|
||||
break;
|
||||
|
||||
case VendingMachineVisualState.Broken:
|
||||
HideLayers(sprite);
|
||||
SetLayerState(VendingMachineVisualLayers.Base, component.BrokenState, sprite);
|
||||
break;
|
||||
|
||||
case VendingMachineVisualState.Off:
|
||||
HideLayers(sprite);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetLayerState(VendingMachineVisualLayers layer, string? state, SpriteComponent sprite)
|
||||
{
|
||||
if (string.IsNullOrEmpty(state))
|
||||
return;
|
||||
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
sprite.LayerSetAutoAnimated(layer, true);
|
||||
sprite.LayerSetState(layer, state);
|
||||
}
|
||||
|
||||
private void PlayAnimation(EntityUid uid, VendingMachineVisualLayers layer, string? state, float animationTime, SpriteComponent sprite)
|
||||
{
|
||||
if (string.IsNullOrEmpty(state))
|
||||
return;
|
||||
|
||||
if (!_animationPlayer.HasRunningAnimation(uid, state))
|
||||
{
|
||||
var animation = GetAnimation(layer, state, animationTime);
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
_animationPlayer.Play(uid, animation, state);
|
||||
}
|
||||
}
|
||||
|
||||
private static Animation GetAnimation(VendingMachineVisualLayers layer, string state, float animationTime)
|
||||
{
|
||||
return new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(animationTime),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
LayerKey = layer,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackSpriteFlick.KeyFrame(state, 0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void HideLayers(SpriteComponent sprite)
|
||||
{
|
||||
HideLayer(VendingMachineVisualLayers.BaseUnshaded, sprite);
|
||||
HideLayer(VendingMachineVisualLayers.Screen, sprite);
|
||||
}
|
||||
|
||||
private static void HideLayer(VendingMachineVisualLayers layer, SpriteComponent sprite)
|
||||
{
|
||||
if (!sprite.LayerMapTryGet(layer, out var actualLayer))
|
||||
return;
|
||||
|
||||
sprite.LayerSetVisible(actualLayer, false);
|
||||
}
|
||||
}
|
||||
|
||||
public enum VendingMachineVisualLayers : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Off / Broken. The other layers will overlay this if the machine is on.
|
||||
/// </summary>
|
||||
Base,
|
||||
/// <summary>
|
||||
/// Normal / Deny / Eject
|
||||
/// </summary>
|
||||
BaseUnshaded,
|
||||
/// <summary>
|
||||
/// Screens that are persistent (where the machine is not off or broken)
|
||||
/// </summary>
|
||||
Screen
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Content.IntegrationTests.Tests
|
||||
}
|
||||
catch (UnknownPrototypeException)
|
||||
{
|
||||
throw new UnknownPrototypeException($"Unknown prototype {item} on vending inventory {vendorProto.Name}");
|
||||
throw new UnknownPrototypeException($"Unknown prototype {item} on vending inventory {vendorProto.ID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Content.Server.VendingMachines;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
{
|
||||
@@ -30,17 +28,15 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
!system.EntityManager.TryGetComponent<TransformComponent>(owner, out var xform))
|
||||
return;
|
||||
|
||||
var throwingsys = system.EntityManager.EntitySysManager.GetEntitySystem<ThrowingSystem>();
|
||||
var totalItems = vendingcomp.AllInventory.Count;
|
||||
var vendingMachineSystem = EntitySystem.Get<VendingMachineSystem>();
|
||||
var inventory = vendingMachineSystem.GetAvailableInventory(owner, vendingcomp);
|
||||
if (inventory.Count <= 0)
|
||||
return;
|
||||
|
||||
var toEject = Math.Min(totalItems * Percent, Max);
|
||||
var toEject = Math.Min(inventory.Count * Percent, Max);
|
||||
for (var i = 0; i < toEject; i++)
|
||||
{
|
||||
var entity = system.EntityManager.SpawnEntity(system.Random.PickAndTake(vendingcomp.AllInventory).ID, xform.Coordinates);
|
||||
|
||||
float range = vendingcomp.NonLimitedEjectRange;
|
||||
Vector2 direction = new Vector2(system.Random.NextFloat(-range, range), system.Random.NextFloat(-range, range));
|
||||
throwingsys.TryThrow(entity, direction, vendingcomp.NonLimitedEjectForce);
|
||||
vendingMachineSystem.EjectRandom(owner, throwItem: true, forceEject: true, vendingcomp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
@@ -9,9 +7,14 @@ namespace Content.Server.VendingMachines
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedVendingMachineComponent))]
|
||||
[Access(typeof(VendingMachineSystem))]
|
||||
public sealed class VendingMachineComponent : SharedVendingMachineComponent
|
||||
{
|
||||
public bool Ejecting;
|
||||
public bool Denying;
|
||||
public bool DispenseOnHitCoolingDown;
|
||||
|
||||
public string? NextItemToEject;
|
||||
|
||||
public bool Broken;
|
||||
|
||||
@@ -21,6 +24,8 @@ namespace Content.Server.VendingMachines
|
||||
[DataField("speedLimiter")]
|
||||
public bool CanShoot = false;
|
||||
|
||||
public bool ThrowNextItem = false;
|
||||
|
||||
/// <summary>
|
||||
/// The chance that a vending machine will randomly dispense an item on hit.
|
||||
/// Chance is 0 if null.
|
||||
@@ -28,24 +33,48 @@ namespace Content.Server.VendingMachines
|
||||
[DataField("dispenseOnHitChance")]
|
||||
public float? DispenseOnHitChance;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum amount of damage that must be done per hit to have a chance
|
||||
/// of dispensing an item.
|
||||
/// </summary>
|
||||
[DataField("dispenseOnHitThreshold")]
|
||||
public float? DispenseOnHitThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of time in seconds that need to pass before damage can cause a vending machine to eject again.
|
||||
/// This value is separate to <see cref="SharedVendingMachineComponent.EjectDelay"/> because that value might be
|
||||
/// 0 for a vending machine for legitimate reasons (no desired delay/no eject animation)
|
||||
/// and can be circumvented with forced ejections.
|
||||
/// </summary>
|
||||
[DataField("dispenseOnHitCooldown")]
|
||||
public float? DispenseOnHitCooldown = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Sound that plays when ejecting an item
|
||||
/// </summary>
|
||||
[DataField("soundVend")]
|
||||
// Grabbed from: https://github.com/discordia-space/CEV-Eris/blob/f702afa271136d093ddeb415423240a2ceb212f0/sound/machines/vending_drop.ogg
|
||||
public SoundSpecifier SoundVend = new SoundPathSpecifier("/Audio/Machines/machine_vend.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Sound that plays when an item can't be ejected
|
||||
/// </summary>
|
||||
[DataField("soundDeny")]
|
||||
// Yoinked from: https://github.com/discordia-space/CEV-Eris/blob/35bbad6764b14e15c03a816e3e89aa1751660ba9/sound/machines/Custom_deny.ogg
|
||||
public SoundSpecifier SoundDeny = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// The action available to the player controlling the vending machine
|
||||
/// </summary>
|
||||
[DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string? Action = "VendingThrow";
|
||||
|
||||
[ViewVariables] public BoundUserInterface? UserInterface => Owner.GetUIOrNull(VendingMachineUiKey.Key);
|
||||
|
||||
public float NonLimitedEjectForce = 7.5f;
|
||||
|
||||
public float NonLimitedEjectRange = 5f;
|
||||
|
||||
public float EjectAccumulator = 0f;
|
||||
public float DenyAccumulator = 0f;
|
||||
public float DispenseOnHitAccumulator = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public sealed class VendingMachineEjectItemWireAction : BaseWireAction
|
||||
{
|
||||
if (EntityManager.TryGetComponent(wire.Owner, out VendingMachineComponent? vending))
|
||||
{
|
||||
vending.CanShoot = true;
|
||||
_vendingMachineSystem.SetShooting(wire.Owner, true, vending);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -52,7 +52,7 @@ public sealed class VendingMachineEjectItemWireAction : BaseWireAction
|
||||
{
|
||||
if (EntityManager.TryGetComponent(wire.Owner, out VendingMachineComponent? vending))
|
||||
{
|
||||
vending.CanShoot = false;
|
||||
_vendingMachineSystem.SetShooting(wire.Owner, false, vending);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Actions;
|
||||
@@ -16,7 +16,6 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using static Content.Shared.VendingMachines.SharedVendingMachineComponent;
|
||||
|
||||
namespace Content.Server.VendingMachines
|
||||
{
|
||||
@@ -28,18 +27,23 @@ namespace Content.Server.VendingMachines
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwingSystem = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _action = default!;
|
||||
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<VendingMachineComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, InventorySyncRequestMessage>(OnInventoryRequestMessage);
|
||||
SubscribeLocalEvent<VendingMachineComponent, VendingMachineEjectMessage>(OnInventoryEjectMessage);
|
||||
SubscribeLocalEvent<VendingMachineComponent, BreakageEventArgs>(OnBreak);
|
||||
SubscribeLocalEvent<VendingMachineComponent, GotEmaggedEvent>(OnEmagged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamage);
|
||||
|
||||
SubscribeLocalEvent<VendingMachineComponent, ActivatableUIOpenAttemptEvent>(OnActivatableUIOpenAttempt);
|
||||
SubscribeLocalEvent<VendingMachineComponent, BoundUIOpenedEvent>(OnBoundUIOpened);
|
||||
SubscribeLocalEvent<VendingMachineComponent, VendingMachineEjectMessage>(OnInventoryEjectMessage);
|
||||
|
||||
SubscribeLocalEvent<VendingMachineComponent, VendingMachineSelfDispenseEvent>(OnSelfDispense);
|
||||
}
|
||||
|
||||
@@ -49,9 +53,9 @@ namespace Content.Server.VendingMachines
|
||||
|
||||
var component = (VendingMachineComponent) sharedComponent;
|
||||
|
||||
if (TryComp<ApcPowerReceiverComponent>(component.Owner, out var receiver))
|
||||
if (HasComp<ApcPowerReceiverComponent>(component.Owner))
|
||||
{
|
||||
TryUpdateVisualState(uid, null, component);
|
||||
TryUpdateVisualState(uid, component);
|
||||
}
|
||||
|
||||
if (component.Action != null)
|
||||
@@ -61,17 +65,22 @@ namespace Content.Server.VendingMachines
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInventoryRequestMessage(EntityUid uid, VendingMachineComponent component, InventorySyncRequestMessage args)
|
||||
private void OnActivatableUIOpenAttempt(EntityUid uid, VendingMachineComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
return;
|
||||
if (component.Broken)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
var inventory = new List<VendingMachineInventoryEntry>(component.Inventory);
|
||||
private void OnBoundUIOpened(EntityUid uid, VendingMachineComponent component, BoundUIOpenedEvent args)
|
||||
{
|
||||
UpdateVendingMachineInterfaceState(component);
|
||||
}
|
||||
|
||||
if (component.Emagged) inventory.AddRange(component.EmaggedInventory);
|
||||
if (component.Contraband) inventory.AddRange(component.ContrabandInventory);
|
||||
private void UpdateVendingMachineInterfaceState(VendingMachineComponent component)
|
||||
{
|
||||
var state = new VendingMachineInterfaceState(GetAllInventory(component.Owner, component));
|
||||
|
||||
component.UserInterface?.SendMessage(new VendingMachineInventoryMessage(inventory));
|
||||
_userInterfaceSystem.TrySetUiState(component.Owner, VendingMachineUiKey.Key, state);
|
||||
}
|
||||
|
||||
private void OnInventoryEjectMessage(EntityUid uid, VendingMachineComponent component, VendingMachineEjectMessage args)
|
||||
@@ -87,13 +96,13 @@ namespace Content.Server.VendingMachines
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, VendingMachineComponent component, PowerChangedEvent args)
|
||||
{
|
||||
TryUpdateVisualState(uid, null, component);
|
||||
TryUpdateVisualState(uid, component);
|
||||
}
|
||||
|
||||
private void OnBreak(EntityUid uid, VendingMachineComponent vendComponent, BreakageEventArgs eventArgs)
|
||||
{
|
||||
vendComponent.Broken = true;
|
||||
TryUpdateVisualState(uid, VendingMachineVisualState.Broken, vendComponent);
|
||||
TryUpdateVisualState(uid, vendComponent);
|
||||
}
|
||||
|
||||
private void OnEmagged(EntityUid uid, VendingMachineComponent component, GotEmaggedEvent args)
|
||||
@@ -107,11 +116,17 @@ namespace Content.Server.VendingMachines
|
||||
|
||||
private void OnDamage(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (component.DispenseOnHitChance == null || args.DamageDelta == null)
|
||||
if (component.Broken || component.DispenseOnHitCoolingDown ||
|
||||
component.DispenseOnHitChance == null || args.DamageDelta == null)
|
||||
return;
|
||||
|
||||
if (args.DamageDelta.Total >= component.DispenseOnHitThreshold && _random.Prob(component.DispenseOnHitChance.Value))
|
||||
EjectRandom(uid, true, component);
|
||||
if (args.DamageIncreased && args.DamageDelta.Total >= component.DispenseOnHitThreshold &&
|
||||
_random.Prob(component.DispenseOnHitChance.Value))
|
||||
{
|
||||
if (component.DispenseOnHitCooldown > 0f)
|
||||
component.DispenseOnHitCoolingDown = true;
|
||||
EjectRandom(uid, throwItem: true, forceEject: true, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelfDispense(EntityUid uid, VendingMachineComponent component, VendingMachineSelfDispenseEvent args)
|
||||
@@ -120,7 +135,18 @@ namespace Content.Server.VendingMachines
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
EjectRandom(uid, true, component);
|
||||
EjectRandom(uid, throwItem: true, forceEject: false, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="VendingMachineComponent.CanShoot"/> property of the vending machine.
|
||||
/// </summary>
|
||||
public void SetShooting(EntityUid uid, bool canShoot, VendingMachineComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.CanShoot = canShoot;
|
||||
}
|
||||
|
||||
public void Deny(EntityUid uid, VendingMachineComponent? vendComponent = null)
|
||||
@@ -128,16 +154,18 @@ namespace Content.Server.VendingMachines
|
||||
if (!Resolve(uid, ref vendComponent))
|
||||
return;
|
||||
|
||||
SoundSystem.Play(vendComponent.SoundDeny.GetSound(), Filter.Pvs(vendComponent.Owner), vendComponent.Owner, AudioParams.Default.WithVolume(-2f));
|
||||
// Play the Deny animation
|
||||
TryUpdateVisualState(uid, VendingMachineVisualState.Deny, vendComponent);
|
||||
//TODO: This duration should be a distinct value specific to the deny animation
|
||||
vendComponent.Owner.SpawnTimer(vendComponent.AnimationDuration, () =>
|
||||
{
|
||||
TryUpdateVisualState(uid, VendingMachineVisualState.Normal, vendComponent);
|
||||
});
|
||||
if (vendComponent.Denying)
|
||||
return;
|
||||
|
||||
vendComponent.Denying = true;
|
||||
_audioSystem.Play(vendComponent.SoundDeny, Filter.Pvs(vendComponent.Owner), vendComponent.Owner, AudioParams.Default.WithVolume(-2f));
|
||||
TryUpdateVisualState(uid, vendComponent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the user is authorized to use this vending machine
|
||||
/// </summary>
|
||||
/// <param name="sender">Entity trying to use the vending machine</param>
|
||||
public bool IsAuthorized(EntityUid uid, EntityUid? sender, VendingMachineComponent? vendComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref vendComponent) || sender == null)
|
||||
@@ -155,6 +183,13 @@ namespace Content.Server.VendingMachines
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to eject the provided item. Will do nothing if the vending machine is incapable of ejecting, already ejecting
|
||||
/// or the item doesn't exist in its inventory.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of inventory the item is from</param>
|
||||
/// <param name="itemId">The prototype ID of the item</param>
|
||||
/// <param name="throwItem">Whether the item should be thrown in a random direction after ejection</param>
|
||||
public void TryEjectVendorItem(EntityUid uid, InventoryType type, string itemId, bool throwItem, VendingMachineComponent? vendComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref vendComponent))
|
||||
@@ -165,13 +200,7 @@ namespace Content.Server.VendingMachines
|
||||
return;
|
||||
}
|
||||
|
||||
var entry = type switch
|
||||
{
|
||||
InventoryType.Regular => vendComponent.Inventory.Find(x => x.ID == itemId),
|
||||
InventoryType.Emagged when vendComponent.Emagged => vendComponent.EmaggedInventory.Find(x => x.ID == itemId),
|
||||
InventoryType.Contraband when vendComponent.Contraband => vendComponent.ContrabandInventory.Find(x => x.ID == itemId),
|
||||
_ => null
|
||||
};
|
||||
var entry = GetEntry(itemId, type, vendComponent);
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
@@ -195,24 +224,20 @@ namespace Content.Server.VendingMachines
|
||||
|
||||
// Start Ejecting, and prevent users from ordering while anim playing
|
||||
vendComponent.Ejecting = true;
|
||||
vendComponent.NextItemToEject = entry.ID;
|
||||
vendComponent.ThrowNextItem = throwItem;
|
||||
entry.Amount--;
|
||||
vendComponent.UserInterface?.SendMessage(new VendingMachineInventoryMessage(vendComponent.AllInventory));
|
||||
TryUpdateVisualState(uid, VendingMachineVisualState.Eject, vendComponent);
|
||||
vendComponent.Owner.SpawnTimer(vendComponent.AnimationDuration, () =>
|
||||
{
|
||||
vendComponent.Ejecting = false;
|
||||
TryUpdateVisualState(uid, VendingMachineVisualState.Normal, vendComponent);
|
||||
var ent = EntityManager.SpawnEntity(entry.ID, transformComp.Coordinates);
|
||||
if (throwItem)
|
||||
{
|
||||
float range = vendComponent.NonLimitedEjectRange;
|
||||
Vector2 direction = new Vector2(_random.NextFloat(-range, range), _random.NextFloat(-range, range));
|
||||
_throwingSystem.TryThrow(ent, direction, vendComponent.NonLimitedEjectForce);
|
||||
}
|
||||
});
|
||||
SoundSystem.Play(vendComponent.SoundVend.GetSound(), Filter.Pvs(vendComponent.Owner), vendComponent.Owner, AudioParams.Default.WithVolume(-2f));
|
||||
UpdateVendingMachineInterfaceState(vendComponent);
|
||||
TryUpdateVisualState(uid, vendComponent);
|
||||
_audioSystem.Play(vendComponent.SoundVend, Filter.Pvs(vendComponent.Owner), vendComponent.Owner, AudioParams.Default.WithVolume(-2f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the user is authorized to use the vending machine, then ejects the provided item if true
|
||||
/// </summary>
|
||||
/// <param name="sender">Entity that is trying to use the vending machine</param>
|
||||
/// <param name="type">The type of inventory the item is from</param>
|
||||
/// <param name="itemId">The prototype ID of the item</param>
|
||||
public void AuthorizedVend(EntityUid uid, EntityUid sender, InventoryType type, string itemId, VendingMachineComponent component)
|
||||
{
|
||||
if (IsAuthorized(uid, sender, component))
|
||||
@@ -221,12 +246,15 @@ namespace Content.Server.VendingMachines
|
||||
}
|
||||
}
|
||||
|
||||
public void TryUpdateVisualState(EntityUid uid, VendingMachineVisualState? state = VendingMachineVisualState.Normal, VendingMachineComponent? vendComponent = null)
|
||||
/// <summary>
|
||||
/// Tries to update the visuals of the component based on its current state.
|
||||
/// </summary>
|
||||
public void TryUpdateVisualState(EntityUid uid, VendingMachineComponent? vendComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref vendComponent))
|
||||
return;
|
||||
|
||||
var finalState = state == null ? VendingMachineVisualState.Normal : state;
|
||||
var finalState = VendingMachineVisualState.Normal;
|
||||
if (vendComponent.Broken)
|
||||
{
|
||||
finalState = VendingMachineVisualState.Broken;
|
||||
@@ -235,6 +263,10 @@ namespace Content.Server.VendingMachines
|
||||
{
|
||||
finalState = VendingMachineVisualState.Eject;
|
||||
}
|
||||
else if (vendComponent.Denying)
|
||||
{
|
||||
finalState = VendingMachineVisualState.Deny;
|
||||
}
|
||||
else if (!this.IsPowered(uid, EntityManager))
|
||||
{
|
||||
finalState = VendingMachineVisualState.Off;
|
||||
@@ -242,23 +274,121 @@ namespace Content.Server.VendingMachines
|
||||
|
||||
if (TryComp<AppearanceComponent>(vendComponent.Owner, out var appearance))
|
||||
{
|
||||
appearance.SetData(VendingMachineVisuals.VisualState, finalState);
|
||||
_appearanceSystem.SetData(uid, VendingMachineVisuals.VisualState, finalState, appearance);
|
||||
}
|
||||
}
|
||||
|
||||
public void EjectRandom(EntityUid uid, bool throwItem, VendingMachineComponent? vendComponent = null)
|
||||
/// <summary>
|
||||
/// Ejects a random item from the available stock. Will do nothing if the vending machine is empty.
|
||||
/// </summary>
|
||||
/// <param name="throwItem">Whether to throw the item in a random direction after dispensing it.</param>
|
||||
/// <param name="forceEject">Whether to skip the regular ejection checks and immediately dispense the item without animation.</param>
|
||||
public void EjectRandom(EntityUid uid, bool throwItem, bool forceEject = false, VendingMachineComponent? vendComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref vendComponent))
|
||||
return;
|
||||
|
||||
var availableItems = vendComponent.AllInventory.Where(x => x.Amount > 0).ToList();
|
||||
var availableItems = GetAvailableInventory(uid, vendComponent);
|
||||
if (availableItems.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = _random.Pick(availableItems);
|
||||
|
||||
if (forceEject)
|
||||
{
|
||||
vendComponent.NextItemToEject = item.ID;
|
||||
vendComponent.ThrowNextItem = throwItem;
|
||||
var entry = GetEntry(item.ID, item.Type, vendComponent);
|
||||
if (entry != null)
|
||||
entry.Amount--;
|
||||
EjectItem(vendComponent, forceEject);
|
||||
}
|
||||
else
|
||||
TryEjectVendorItem(uid, item.Type, item.ID, throwItem, vendComponent);
|
||||
}
|
||||
|
||||
private void EjectItem(VendingMachineComponent vendComponent, bool forceEject = false)
|
||||
{
|
||||
// No need to update the visual state because we never changed it during a forced eject
|
||||
if (!forceEject)
|
||||
TryUpdateVisualState(vendComponent.Owner, vendComponent);
|
||||
|
||||
if (string.IsNullOrEmpty(vendComponent.NextItemToEject))
|
||||
{
|
||||
vendComponent.ThrowNextItem = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var ent = EntityManager.SpawnEntity(vendComponent.NextItemToEject, Transform(vendComponent.Owner).Coordinates);
|
||||
if (vendComponent.ThrowNextItem)
|
||||
{
|
||||
var range = vendComponent.NonLimitedEjectRange;
|
||||
var direction = new Vector2(_random.NextFloat(-range, range), _random.NextFloat(-range, range));
|
||||
_throwingSystem.TryThrow(ent, direction, vendComponent.NonLimitedEjectForce);
|
||||
}
|
||||
|
||||
vendComponent.NextItemToEject = null;
|
||||
vendComponent.ThrowNextItem = false;
|
||||
}
|
||||
|
||||
private void DenyItem(VendingMachineComponent vendComponent)
|
||||
{
|
||||
TryUpdateVisualState(vendComponent.Owner, vendComponent);
|
||||
}
|
||||
|
||||
private VendingMachineInventoryEntry? GetEntry(string entryId, InventoryType type, VendingMachineComponent component)
|
||||
{
|
||||
if (type == InventoryType.Emagged && component.Emagged)
|
||||
return component.EmaggedInventory.GetValueOrDefault(entryId);
|
||||
|
||||
if (type == InventoryType.Contraband && component.Contraband)
|
||||
return component.ContrabandInventory.GetValueOrDefault(entryId);
|
||||
|
||||
return component.Inventory.GetValueOrDefault(entryId);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var comp in EntityQuery<VendingMachineComponent>())
|
||||
{
|
||||
if (comp.Ejecting)
|
||||
{
|
||||
comp.EjectAccumulator += frameTime;
|
||||
if (comp.EjectAccumulator >= comp.EjectDelay)
|
||||
{
|
||||
comp.EjectAccumulator = 0f;
|
||||
comp.Ejecting = false;
|
||||
|
||||
EjectItem(comp);
|
||||
}
|
||||
}
|
||||
|
||||
if (comp.Denying)
|
||||
{
|
||||
comp.DenyAccumulator += frameTime;
|
||||
if (comp.DenyAccumulator >= comp.DenyDelay)
|
||||
{
|
||||
comp.DenyAccumulator = 0f;
|
||||
comp.Denying = false;
|
||||
|
||||
DenyItem(comp);
|
||||
}
|
||||
}
|
||||
|
||||
if (comp.DispenseOnHitCoolingDown)
|
||||
{
|
||||
comp.DispenseOnHitAccumulator += frameTime;
|
||||
if (comp.DispenseOnHitAccumulator >= comp.DispenseOnHitCooldown)
|
||||
{
|
||||
comp.DispenseOnHitAccumulator = 0f;
|
||||
comp.DispenseOnHitCoolingDown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,85 +8,45 @@ namespace Content.Shared.VendingMachines
|
||||
[NetworkedComponent()]
|
||||
public abstract class SharedVendingMachineComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// PrototypeID for the vending machine's inventory, see <see cref="VendingMachineInventoryPrototype"/>
|
||||
/// </summary>
|
||||
[DataField("pack", customTypeSerializer: typeof(PrototypeIdSerializer<VendingMachineInventoryPrototype>))]
|
||||
public string PackPrototypeId = string.Empty;
|
||||
|
||||
public TimeSpan AnimationDuration = TimeSpan.Zero;
|
||||
/// <summary>
|
||||
/// Used by the server to determine how long the vending machine stays in the "Deny" state.
|
||||
/// Used by the client to determine how long the deny animation should be played.
|
||||
/// </summary>
|
||||
[DataField("denyDelay")]
|
||||
public float DenyDelay = 2.0f;
|
||||
|
||||
[ViewVariables] public List<VendingMachineInventoryEntry> Inventory = new();
|
||||
[ViewVariables] public List<VendingMachineInventoryEntry> EmaggedInventory = new();
|
||||
[ViewVariables] public List<VendingMachineInventoryEntry> ContrabandInventory = new();
|
||||
/// <summary>
|
||||
/// Used by the server to determine how long the vending machine stays in the "Eject" state.
|
||||
/// The selected item is dispensed afer this delay.
|
||||
/// Used by the client to determine how long the deny animation should be played.
|
||||
/// </summary>
|
||||
[DataField("ejectDelay")]
|
||||
public float EjectDelay = 1.2f;
|
||||
|
||||
public List<VendingMachineInventoryEntry> AllInventory
|
||||
{
|
||||
get
|
||||
{
|
||||
var inventory = new List<VendingMachineInventoryEntry>(Inventory);
|
||||
[ViewVariables]
|
||||
public Dictionary<string, VendingMachineInventoryEntry> Inventory = new();
|
||||
|
||||
if (Emagged) inventory.AddRange(EmaggedInventory);
|
||||
if (Contraband) inventory.AddRange(ContrabandInventory);
|
||||
[ViewVariables]
|
||||
public Dictionary<string, VendingMachineInventoryEntry> EmaggedInventory = new();
|
||||
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
[ViewVariables]
|
||||
public Dictionary<string, VendingMachineInventoryEntry> ContrabandInventory = new();
|
||||
|
||||
public bool Emagged;
|
||||
public bool Contraband;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineVisuals
|
||||
{
|
||||
VisualState,
|
||||
Inventory,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineVisualState
|
||||
{
|
||||
Normal,
|
||||
Off,
|
||||
Broken,
|
||||
Eject,
|
||||
Deny,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class VendingMachineEjectMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly InventoryType Type;
|
||||
public readonly string ID;
|
||||
public VendingMachineEjectMessage(InventoryType type, string id)
|
||||
{
|
||||
Type = type;
|
||||
ID = id;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineUiKey
|
||||
{
|
||||
Key,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class InventorySyncRequestMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class VendingMachineInventoryMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly List<VendingMachineInventoryEntry> Inventory;
|
||||
public VendingMachineInventoryMessage(List<VendingMachineInventoryEntry> inventory)
|
||||
{
|
||||
Inventory = inventory;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class VendingMachineInventoryEntry
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)] public InventoryType Type;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public InventoryType Type;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string ID;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -98,14 +58,6 @@ namespace Content.Shared.VendingMachines
|
||||
Amount = amount;
|
||||
}
|
||||
}
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineWireStatus : byte
|
||||
{
|
||||
Power,
|
||||
Access,
|
||||
Advertisement,
|
||||
Limiter
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum InventoryType : byte
|
||||
@@ -114,6 +66,21 @@ namespace Content.Shared.VendingMachines
|
||||
Emagged,
|
||||
Contraband
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineVisuals
|
||||
{
|
||||
VisualState
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineVisualState
|
||||
{
|
||||
Normal,
|
||||
Off,
|
||||
Broken,
|
||||
Eject,
|
||||
Deny,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
using static Content.Shared.VendingMachines.SharedVendingMachineComponent;
|
||||
|
||||
namespace Content.Shared.VendingMachines;
|
||||
@@ -18,17 +19,43 @@ public abstract class SharedVendingMachineSystem : EntitySystem
|
||||
if (!_prototypeManager.TryIndex(component.PackPrototypeId, out VendingMachineInventoryPrototype? packPrototype))
|
||||
return;
|
||||
|
||||
MetaData(uid).EntityName = packPrototype.Name;
|
||||
component.AnimationDuration = TimeSpan.FromSeconds(packPrototype.AnimationDuration);
|
||||
|
||||
if (TryComp(component.Owner, out AppearanceComponent? appearance))
|
||||
appearance.SetData(VendingMachineVisuals.Inventory, component.PackPrototypeId);
|
||||
|
||||
AddInventoryFromPrototype(uid, packPrototype.StartingInventory, InventoryType.Regular, component);
|
||||
AddInventoryFromPrototype(uid, packPrototype.EmaggedInventory, InventoryType.Emagged, component);
|
||||
AddInventoryFromPrototype(uid, packPrototype.ContrabandInventory, InventoryType.Contraband, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all of the vending machine's inventory. Only includes emagged and contraband inventories if
|
||||
/// <see cref="SharedVendingMachineComponent.Emagged"/> and <see cref="SharedVendingMachineComponent.Contraband"/>
|
||||
/// are <c>true</c> respectively.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <returns></returns>
|
||||
public List<VendingMachineInventoryEntry> GetAllInventory(EntityUid uid, SharedVendingMachineComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return new();
|
||||
|
||||
var inventory = new List<VendingMachineInventoryEntry>(component.Inventory.Values);
|
||||
|
||||
if (component.Emagged)
|
||||
inventory.AddRange(component.EmaggedInventory.Values);
|
||||
|
||||
if (component.Contraband)
|
||||
inventory.AddRange(component.ContrabandInventory.Values);
|
||||
|
||||
return inventory;
|
||||
}
|
||||
|
||||
public List<VendingMachineInventoryEntry> GetAvailableInventory(EntityUid uid, SharedVendingMachineComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return new();
|
||||
|
||||
return GetAllInventory(uid, component).Where(_ => _.Amount > 0).ToList();
|
||||
}
|
||||
|
||||
private void AddInventoryFromPrototype(EntityUid uid, Dictionary<string, uint>? entries,
|
||||
InventoryType type,
|
||||
SharedVendingMachineComponent? component = null)
|
||||
@@ -38,26 +65,26 @@ public abstract class SharedVendingMachineSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
var inventory = new List<VendingMachineInventoryEntry>();
|
||||
var inventory = new Dictionary<string, VendingMachineInventoryEntry>();
|
||||
|
||||
foreach (var (id, amount) in entries)
|
||||
{
|
||||
if (_prototypeManager.HasIndex<EntityPrototype>(id))
|
||||
{
|
||||
inventory.Add(new VendingMachineInventoryEntry(type, id, amount));
|
||||
inventory.Add(id, new VendingMachineInventoryEntry(type, id, amount));
|
||||
}
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case InventoryType.Regular:
|
||||
component.Inventory.AddRange(inventory);
|
||||
component.Inventory = inventory;
|
||||
break;
|
||||
case InventoryType.Emagged:
|
||||
component.EmaggedInventory.AddRange(inventory);
|
||||
component.EmaggedInventory = inventory;
|
||||
break;
|
||||
case InventoryType.Contraband:
|
||||
component.ContrabandInventory.AddRange(inventory);
|
||||
component.ContrabandInventory = inventory;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.VendingMachines
|
||||
{
|
||||
[NetSerializable, Serializable]
|
||||
public sealed class VendingMachineInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public List<VendingMachineInventoryEntry> Inventory;
|
||||
|
||||
public VendingMachineInterfaceState(List<VendingMachineInventoryEntry> inventory)
|
||||
{
|
||||
Inventory = inventory;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class VendingMachineEjectMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly InventoryType Type;
|
||||
public readonly string ID;
|
||||
public VendingMachineEjectMessage(InventoryType type, string id)
|
||||
{
|
||||
Type = type;
|
||||
ID = id;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineUiKey
|
||||
{
|
||||
Key,
|
||||
}
|
||||
}
|
||||
@@ -7,19 +7,9 @@ namespace Content.Shared.VendingMachines
|
||||
public sealed class VendingMachineInventoryPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataFieldAttribute]
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[DataField("name")]
|
||||
public string Name { get; } = string.Empty;
|
||||
|
||||
[DataField("animationDuration")]
|
||||
public double AnimationDuration { get; }
|
||||
|
||||
// TODO make this a proper sprite specifier for yaml linting.
|
||||
[DataField("spriteName")]
|
||||
public string SpriteName { get; } = string.Empty;
|
||||
|
||||
[DataField("startingInventory")]
|
||||
public Dictionary<string, uint> StartingInventory { get; } = new();
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: AmmoVendInventory
|
||||
name: Ammovend
|
||||
spriteName: ammo
|
||||
startingInventory:
|
||||
MagazineBoxCaselessRifle: 3
|
||||
MagazineBoxCaselessRifleHighVelocity: 3
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: AtmosDrobeInventory
|
||||
name: AtmosDrobe
|
||||
spriteName: atmosdrobe
|
||||
startingInventory:
|
||||
ClothingBackpackDuffelEngineering: 2
|
||||
ClothingBackpackSatchelEngineering: 2
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: BarDrobeInventory
|
||||
name: BarDrobe
|
||||
spriteName: bardrobe
|
||||
startingInventory:
|
||||
ClothingHeadHatTophat: 2
|
||||
ClothingEyesGlassesBeer: 2
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: BoozeOMatInventory
|
||||
name: Booze-O-Mat
|
||||
spriteName: boozeomat
|
||||
startingInventory:
|
||||
|
||||
DrinkGlass: 30 #Kept glasses at top for ease to differentiate from booze.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: CargoDrobeInventory
|
||||
name: CargoDrobe
|
||||
spriteName: cargodrobe
|
||||
startingInventory:
|
||||
AppraisalTool: 3
|
||||
ClothingUniformJumpsuitCargo: 3
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: PTechInventory
|
||||
name: PTech
|
||||
spriteName: cart
|
||||
startingInventory:
|
||||
PassengerPDA: 5
|
||||
ClearPDA: 5
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: ChangInventory
|
||||
name: Mr. Chang
|
||||
animationDuration: 2.1
|
||||
spriteName: cigs
|
||||
startingInventory:
|
||||
FoodCondimentPacketSoy: 5
|
||||
FoodSnackCookieFortune: 5
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: PietyVendInventory
|
||||
name: PietyVend
|
||||
spriteName: chapel
|
||||
startingInventory:
|
||||
ClothingUniformJumpsuitChaplain: 2
|
||||
ClothingUniformJumpskirtChaplain: 2
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: ChefDrobeInventory
|
||||
name: ChefDrobe
|
||||
spriteName: chefdrobe
|
||||
startingInventory:
|
||||
ClothingHeadsetService: 2
|
||||
ClothingOuterApronChef: 3
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: ChemDrobeInventory
|
||||
name: ChemDrobe
|
||||
spriteName: chemdrobe
|
||||
startingInventory:
|
||||
ClothingUniformJumpsuitChemistry: 2
|
||||
ClothingUniformJumpskirtChemistry: 2
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: CigaretteMachineInventory
|
||||
name: cigarette machine
|
||||
animationDuration: 2.1
|
||||
spriteName: cigs
|
||||
startingInventory:
|
||||
CigPackGreen: 2
|
||||
CigPackRed: 2
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: ClothesMateInventory
|
||||
name: ClothesMate
|
||||
startingInventory:
|
||||
ClothingBackpack: 5
|
||||
ClothingBackpackDuffel: 5
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: HotDrinksMachineInventory
|
||||
name: Hot drinks machine
|
||||
animationDuration: 3.4
|
||||
spriteName: coffee
|
||||
startingInventory:
|
||||
DrinkHotCoffee: 10
|
||||
DrinkTeacup: 10
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: RobustSoftdrinksInventory
|
||||
name: Robust Softdrinks
|
||||
animationDuration: 1.1
|
||||
spriteName: cola
|
||||
startingInventory:
|
||||
DrinkColaCan: 2
|
||||
DrinkEnergyDrinkCan: 2
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: DetDrobeInventory
|
||||
name: DetDrobe
|
||||
spriteName: detdrobe
|
||||
startingInventory:
|
||||
ClothingUniformJumpsuitDetective: 2
|
||||
ClothingUniformJumpskirtDetective: 2
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: DinnerwareInventory
|
||||
name: Dinnerware
|
||||
spriteName: dinnerware
|
||||
startingInventory:
|
||||
ButchCleaver: 1
|
||||
KitchenKnife: 5
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: DiscountDansInventory
|
||||
name: Discount Dan's
|
||||
spriteName: discount
|
||||
startingInventory:
|
||||
FoodSnackCheesie: 3
|
||||
FoodSnackChips: 3
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: DonutInventory
|
||||
name: Donut
|
||||
spriteName: donut
|
||||
startingInventory:
|
||||
FoodDonutChocolate: 5
|
||||
FoodDonutApple: 3
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
- type: vendingMachineInventory
|
||||
id: EmptyVendingMachineInventory
|
||||
name: Empty vending machine
|
||||
spriteName: empty
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: EngiDrobeInventory
|
||||
name: EngiDrobe
|
||||
spriteName: engidrobe
|
||||
startingInventory:
|
||||
ClothingBackpackDuffelEngineering: 3
|
||||
ClothingBackpackSatchelEngineering: 3
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: EngiVendInventory
|
||||
name: Engi-Vend
|
||||
animationDuration: 2.1
|
||||
spriteName: engivend
|
||||
startingInventory:
|
||||
ClothingEyesGlassesMeson: 4
|
||||
ClothingHeadHatWelding: 4
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: GoodCleanFunInventory
|
||||
name: Good Clean Fun
|
||||
animationDuration: 1.3
|
||||
spriteName: games
|
||||
startingInventory:
|
||||
DiceBag: 4
|
||||
Paper: 8
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: GeneDrobeInventory
|
||||
name: GeneDrobe
|
||||
spriteName: genedrobe
|
||||
startingInventory:
|
||||
ClothingShoesColorWhite: 2
|
||||
ClothingOuterCoatLab: 2
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: HyDrobeInventory
|
||||
name: HyDrobe
|
||||
spriteName: hydrobe
|
||||
startingInventory:
|
||||
ClothingBackpackHydroponics: 2
|
||||
ClothingBackpackSatchelHydroponics: 2
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: JaniDrobeInventory
|
||||
name: JaniDrobe
|
||||
spriteName: janidrobe
|
||||
startingInventory:
|
||||
ClothingUniformJumpsuitJanitor: 2
|
||||
ClothingUniformJumpskirtJanitor: 2
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: LawDrobeInventory
|
||||
name: LawDrobe
|
||||
spriteName: lawdrobe
|
||||
startingInventory:
|
||||
ClothingUniformJumpsuitLawyerBlue: 1
|
||||
ClothingUniformJumpsuitLawyerPurple: 1
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: MagiVendInventory
|
||||
name: MagiVend
|
||||
animationDuration: 1.5
|
||||
spriteName: magivend
|
||||
startingInventory:
|
||||
ClothingHeadHatWizard: 3
|
||||
ClothingOuterWizard: 3
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: NanoMedPlusInventory
|
||||
name: NanoMed Plus
|
||||
animationDuration: 1.8
|
||||
spriteName: medical
|
||||
startingInventory:
|
||||
HandheldHealthAnalyzer: 3
|
||||
Brutepack: 5
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: MediDrobeInventory
|
||||
name: MediDrobe
|
||||
spriteName: medidrobe
|
||||
startingInventory:
|
||||
ClothingBackpackDuffelMedical: 4
|
||||
ClothingBackpackMedical: 4
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: NutriMaxInventory
|
||||
name: NutriMax
|
||||
spriteName: nutri
|
||||
startingInventory:
|
||||
HydroponicsToolSpade: 2
|
||||
HydroponicsToolMiniHoe: 2
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: RoboDrobeInventory
|
||||
name: RoboDrobe
|
||||
spriteName: robodrobe
|
||||
startingInventory:
|
||||
ClothingOuterCoatLab: 2
|
||||
ClothingShoesColorBlack: 2
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: RobotechDeluxeInventory
|
||||
name: Robotech Deluxe
|
||||
spriteName: robotics
|
||||
startingInventory:
|
||||
#TO DO: add missing prototypes
|
||||
ClothingOuterCoatLab: 4
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: SalvageEquipmentInventory
|
||||
name: Salvage Equipment
|
||||
spriteName: mining
|
||||
startingInventory:
|
||||
Crowbar: 2
|
||||
Pickaxe: 4
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: SciDrobeInventory
|
||||
name: SciDrobe
|
||||
spriteName: scidrobe
|
||||
startingInventory:
|
||||
ClothingBackpackScience: 3
|
||||
ClothingBackpackSatchelScience: 3
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: SecTechInventory
|
||||
name: SecTech
|
||||
animationDuration: 2.8
|
||||
spriteName: sec
|
||||
startingInventory:
|
||||
Handcuffs: 8
|
||||
GrenadeFlashBang: 4
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: SecDrobeInventory
|
||||
name: SecDrobe
|
||||
spriteName: secdrobe
|
||||
startingInventory:
|
||||
ClothingBackpackSecurity: 3
|
||||
ClothingBackpackSatchelSecurity: 3
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: MegaSeedServitorInventory
|
||||
name: MegaSeed Servitor
|
||||
animationDuration: 1.3
|
||||
spriteName: seeds
|
||||
startingInventory:
|
||||
AloeSeeds: 3
|
||||
AmbrosiaVulgarisSeeds: 3
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
- type: vendingMachineInventory
|
||||
id: SmartFridgeInventory
|
||||
name: SmartFridge
|
||||
spriteName: smartfridge
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: GetmoreChocolateCorpInventory
|
||||
name: Getmore Chocolate Corp
|
||||
animationDuration: 0.5
|
||||
spriteName: snack
|
||||
startingInventory:
|
||||
FoodSnackRaisins: 3
|
||||
FoodSnackChocolate: 3
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
- type: vendingMachineInventory
|
||||
id: BodaInventory
|
||||
name: BODA
|
||||
spriteName: sovietsoda
|
||||
startingInventory:
|
||||
DrinkColaCan: 10 #typically hacked product. Default product is "soda"
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
- type: vendingMachineInventory
|
||||
id: TankDispenserEVAInventory
|
||||
name: tank dispenser
|
||||
spriteName: tankdispenser
|
||||
startingInventory:
|
||||
OxygenTankFilled: 5
|
||||
NitrogenTankFilled: 5
|
||||
|
||||
- type: vendingMachineInventory
|
||||
id: TankDispenserEngineeringInventory
|
||||
name: tank dispenser
|
||||
spriteName: tankdispenser
|
||||
startingInventory:
|
||||
PlasmaTankFilled: 5
|
||||
OxygenTankFilled: 5
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: AutoDrobeInventory
|
||||
name: AutoDrobe
|
||||
spriteName: theater
|
||||
startingInventory:
|
||||
ClothingOuterWinterClown: 1
|
||||
ClothingOuterWinterMime: 1
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
- type: vendingMachineInventory
|
||||
id: VendomatInventory
|
||||
name: Vendomat
|
||||
spriteName: vendomat
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: ViroDrobeInventory
|
||||
name: ViroDrobe
|
||||
spriteName: virodrobe
|
||||
startingInventory:
|
||||
ClothingShoesColorWhite: 2
|
||||
ClothingOuterCoatLab: 2
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: NanoMedInventory
|
||||
name: NanoMed
|
||||
spriteName: wallmed
|
||||
startingInventory:
|
||||
Brutepack: 5
|
||||
Ointment: 5
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
- type: vendingMachineInventory
|
||||
id: YouToolInventory
|
||||
name: YouTool
|
||||
animationDuration: 1.1
|
||||
spriteName: youtool
|
||||
startingInventory:
|
||||
CableApcStack: 5
|
||||
Crowbar: 5
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,9 @@
|
||||
},
|
||||
{
|
||||
"name": "off"
|
||||
},
|
||||
{
|
||||
"name": "panel"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user