mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
VendingMachine cleanup and small refactor (#44839)
* just cleanup * remove initialize
This commit is contained in:
parent
ae9e947d8a
commit
aa64ee8b77
12 changed files with 723 additions and 760 deletions
|
|
@ -1,4 +1,3 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.VendingMachines;
|
||||
|
|
@ -10,186 +9,184 @@ using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
|
|||
using Robust.Client.UserInterface;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.VendingMachines.UI
|
||||
namespace Content.Client.VendingMachines.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class VendingMachineMenu : FancyWindow
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class VendingMachineMenu : FancyWindow
|
||||
[Dependency] private IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private IEntityManager _entityManager = default!;
|
||||
|
||||
private readonly Dictionary<EntProtoId, EntityUid> _dummies = [];
|
||||
private readonly Dictionary<EntProtoId, (ListContainerButton Button, VendingMachineItem Item)> _listItems = new();
|
||||
private readonly Dictionary<EntProtoId, uint> _amounts = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the vending machine is able to be interacted with or not.
|
||||
/// </summary>
|
||||
private bool _enabled;
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnItemSelected;
|
||||
|
||||
public VendingMachineMenu()
|
||||
{
|
||||
[Dependency] private IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private IEntityManager _entityManager = default!;
|
||||
MinSize = SetSize = new Vector2(250, 150);
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
private readonly Dictionary<EntProtoId, EntityUid> _dummies = [];
|
||||
private readonly Dictionary<EntProtoId, (ListContainerButton Button, VendingMachineItem Item)> _listItems = new();
|
||||
private readonly Dictionary<EntProtoId, uint> _amounts = new();
|
||||
VendingContents.SearchBar = SearchBar;
|
||||
VendingContents.DataFilterCondition += DataFilterCondition;
|
||||
VendingContents.GenerateItem += GenerateButton;
|
||||
VendingContents.ItemKeyBindDown += (args, data) => OnItemSelected?.Invoke(args, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the vending machine is able to be interacted with or not.
|
||||
/// </summary>
|
||||
private bool _enabled;
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnItemSelected;
|
||||
// Don't clean up dummies during disposal or we'll just have to spawn them again
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
public VendingMachineMenu()
|
||||
// Delete any dummy items we spawned
|
||||
foreach (var entity in _dummies.Values)
|
||||
{
|
||||
MinSize = SetSize = new Vector2(250, 150);
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
VendingContents.SearchBar = SearchBar;
|
||||
VendingContents.DataFilterCondition += DataFilterCondition;
|
||||
VendingContents.GenerateItem += GenerateButton;
|
||||
VendingContents.ItemKeyBindDown += (args, data) => OnItemSelected?.Invoke(args, data);
|
||||
_entityManager.QueueDeleteEntity(entity);
|
||||
}
|
||||
_dummies.Clear();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
private bool DataFilterCondition(string filter, ListData data)
|
||||
{
|
||||
if (data is not VendorItemsListData { ItemText: var text })
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
return true;
|
||||
|
||||
return text.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
private void GenerateButton(ListData data, ListContainerButton button)
|
||||
{
|
||||
if (data is not VendorItemsListData { ItemProtoID: var protoID, ItemText: var text })
|
||||
return;
|
||||
|
||||
var item = new VendingMachineItem(protoID, text);
|
||||
_listItems[protoID] = (button, item);
|
||||
button.AddChild(item);
|
||||
button.AddStyleClass(StyleClass.ButtonSquare);
|
||||
button.Disabled = !_enabled || _amounts[protoID] == 0;
|
||||
}
|
||||
|
||||
/// <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, bool enabled)
|
||||
{
|
||||
_enabled = enabled;
|
||||
_listItems.Clear();
|
||||
_amounts.Clear();
|
||||
|
||||
if (inventory.Count == 0 && VendingContents.Visible)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
SearchBar.Visible = false;
|
||||
VendingContents.Visible = false;
|
||||
|
||||
// Don't clean up dummies during disposal or we'll just have to spawn them again
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
// Delete any dummy items we spawned
|
||||
foreach (var entity in _dummies.Values)
|
||||
var outOfStockLabel = new Label
|
||||
{
|
||||
_entityManager.QueueDeleteEntity(entity);
|
||||
}
|
||||
_dummies.Clear();
|
||||
Text = Loc.GetString("vending-machine-component-try-eject-out-of-stock"),
|
||||
Margin = new Thickness(4, 4),
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Stretch,
|
||||
HorizontalAlignment = HAlignment.Center
|
||||
};
|
||||
|
||||
MainContainer.AddChild(outOfStockLabel);
|
||||
|
||||
SetSizeAfterUpdate(outOfStockLabel.Text.Length, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private bool DataFilterCondition(string filter, ListData data)
|
||||
var longestEntry = string.Empty;
|
||||
var listData = new List<VendorItemsListData>();
|
||||
|
||||
for (var i = 0; i < inventory.Count; i++)
|
||||
{
|
||||
if (data is not VendorItemsListData { ItemText: var text })
|
||||
return false;
|
||||
var entry = inventory[i];
|
||||
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
return true;
|
||||
|
||||
return text.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
private void GenerateButton(ListData data, ListContainerButton button)
|
||||
{
|
||||
if (data is not VendorItemsListData { ItemProtoID: var protoID, ItemText: var text })
|
||||
return;
|
||||
|
||||
var item = new VendingMachineItem(protoID, text);
|
||||
_listItems[protoID] = (button, item);
|
||||
button.AddChild(item);
|
||||
button.AddStyleClass(StyleClass.ButtonSquare);
|
||||
button.Disabled = !_enabled || _amounts[protoID] == 0;
|
||||
}
|
||||
|
||||
/// <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, bool enabled)
|
||||
{
|
||||
_enabled = enabled;
|
||||
_listItems.Clear();
|
||||
_amounts.Clear();
|
||||
|
||||
if (inventory.Count == 0 && VendingContents.Visible)
|
||||
if (!_prototypeManager.Resolve(entry.ID, out var prototype))
|
||||
{
|
||||
SearchBar.Visible = false;
|
||||
VendingContents.Visible = false;
|
||||
|
||||
var outOfStockLabel = new Label()
|
||||
{
|
||||
Text = Loc.GetString("vending-machine-component-try-eject-out-of-stock"),
|
||||
Margin = new Thickness(4, 4),
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Stretch,
|
||||
HorizontalAlignment = HAlignment.Center
|
||||
};
|
||||
|
||||
MainContainer.AddChild(outOfStockLabel);
|
||||
|
||||
SetSizeAfterUpdate(outOfStockLabel.Text.Length, 0);
|
||||
|
||||
return;
|
||||
_amounts[entry.ID] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
var longestEntry = string.Empty;
|
||||
var listData = new List<VendorItemsListData>();
|
||||
|
||||
for (var i = 0; i < inventory.Count; i++)
|
||||
if (!_dummies.TryGetValue(entry.ID, out var dummy))
|
||||
{
|
||||
var entry = inventory[i];
|
||||
|
||||
if (!_prototypeManager.Resolve(entry.ID, out var prototype))
|
||||
{
|
||||
_amounts[entry.ID] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_dummies.TryGetValue(entry.ID, out var dummy))
|
||||
{
|
||||
dummy = _entityManager.Spawn(entry.ID);
|
||||
_dummies.Add(entry.ID, dummy);
|
||||
}
|
||||
|
||||
var itemName = Identity.Name(dummy, _entityManager);
|
||||
var itemText = $"{itemName} [{entry.Amount}]";
|
||||
_amounts[entry.ID] = entry.Amount;
|
||||
|
||||
if (itemText.Length > longestEntry.Length)
|
||||
longestEntry = itemText;
|
||||
|
||||
listData.Add(new VendorItemsListData(prototype.ID, i)
|
||||
{
|
||||
ItemText = itemText,
|
||||
});
|
||||
dummy = _entityManager.Spawn(entry.ID);
|
||||
_dummies.Add(entry.ID, dummy);
|
||||
}
|
||||
|
||||
VendingContents.PopulateList(listData);
|
||||
|
||||
SetSizeAfterUpdate(longestEntry.Length, inventory.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates text entries for vending data in place without modifying the list controls.
|
||||
/// </summary>
|
||||
public void UpdateAmounts(List<VendingMachineInventoryEntry> cachedInventory, bool enabled)
|
||||
{
|
||||
_enabled = enabled;
|
||||
|
||||
foreach (var proto in _dummies.Keys)
|
||||
{
|
||||
if (!_listItems.TryGetValue(proto, out var button))
|
||||
continue;
|
||||
|
||||
var dummy = _dummies[proto];
|
||||
if (!cachedInventory.TryFirstOrDefault(o => o.ID == proto, out var entry))
|
||||
continue;
|
||||
var amount = entry.Amount;
|
||||
// Could be better? Problem is all inventory entries get squashed.
|
||||
var text = GetItemText(dummy, amount);
|
||||
|
||||
button.Item.SetText(text);
|
||||
button.Button.Disabled = !enabled || amount == 0;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetItemText(EntityUid dummy, uint amount)
|
||||
{
|
||||
var itemName = Identity.Name(dummy, _entityManager);
|
||||
return $"{itemName} [{amount}]";
|
||||
var itemText = $"{itemName} [{entry.Amount}]";
|
||||
_amounts[entry.ID] = entry.Amount;
|
||||
|
||||
if (itemText.Length > longestEntry.Length)
|
||||
longestEntry = itemText;
|
||||
|
||||
listData.Add(new VendorItemsListData(prototype.ID, i)
|
||||
{
|
||||
ItemText = itemText,
|
||||
});
|
||||
}
|
||||
|
||||
private void SetSizeAfterUpdate(int longestEntryLength, int contentCount)
|
||||
VendingContents.PopulateList(listData);
|
||||
|
||||
SetSizeAfterUpdate(longestEntry.Length, inventory.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates text entries for vending data in place without modifying the list controls.
|
||||
/// </summary>
|
||||
public void UpdateAmounts(List<VendingMachineInventoryEntry> cachedInventory, bool enabled)
|
||||
{
|
||||
_enabled = enabled;
|
||||
|
||||
foreach (var proto in _dummies.Keys)
|
||||
{
|
||||
SetSize = new Vector2(Math.Clamp((longestEntryLength + 2) * 12, 250, 400),
|
||||
Math.Clamp(contentCount * 50, 150, 350));
|
||||
if (!_listItems.TryGetValue(proto, out var button))
|
||||
continue;
|
||||
|
||||
var dummy = _dummies[proto];
|
||||
if (!cachedInventory.TryFirstOrDefault(o => o.ID == proto, out var entry))
|
||||
continue;
|
||||
var amount = entry.Amount;
|
||||
// Could be better? Problem is all inventory entries get squashed.
|
||||
var text = GetItemText(dummy, amount);
|
||||
|
||||
button.Item.SetText(text);
|
||||
button.Button.Disabled = !enabled || amount == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public record VendorItemsListData(EntProtoId ItemProtoID, int ItemIndex) : ListData
|
||||
private string GetItemText(EntityUid dummy, uint amount)
|
||||
{
|
||||
public string ItemText = string.Empty;
|
||||
var itemName = Identity.Name(dummy, _entityManager);
|
||||
return $"{itemName} [{amount}]";
|
||||
}
|
||||
|
||||
private void SetSizeAfterUpdate(int longestEntryLength, int contentCount)
|
||||
{
|
||||
SetSize = new Vector2(Math.Clamp((longestEntryLength + 2) * 12, 250, 400),
|
||||
Math.Clamp(contentCount * 50, 150, 350));
|
||||
}
|
||||
}
|
||||
|
||||
public record VendorItemsListData(EntProtoId ItemProtoID, int ItemIndex) : ListData
|
||||
{
|
||||
public string ItemText = string.Empty;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,80 +5,75 @@ using Robust.Client.UserInterface;
|
|||
using Robust.Shared.Input;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.VendingMachines
|
||||
namespace Content.Client.VendingMachines;
|
||||
|
||||
public sealed class VendingMachineBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
public sealed class VendingMachineBoundUserInterface : BoundUserInterface
|
||||
[ViewVariables]
|
||||
private VendingMachineMenu? _menu;
|
||||
|
||||
[ViewVariables]
|
||||
private List<VendingMachineInventoryEntry> _cachedInventory = new();
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
[ViewVariables]
|
||||
private VendingMachineMenu? _menu;
|
||||
base.Open();
|
||||
|
||||
[ViewVariables]
|
||||
private List<VendingMachineInventoryEntry> _cachedInventory = new();
|
||||
_menu = this.CreateWindowCenteredLeft<VendingMachineMenu>();
|
||||
_menu.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
_menu.OnItemSelected += OnItemSelected;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public VendingMachineBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
public void Refresh()
|
||||
{
|
||||
var enabled = EntMan.TryGetComponent(Owner, out VendingMachineComponent? bendy) && !bendy.Ejecting;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
var system = EntMan.System<VendingMachineSystem>();
|
||||
_cachedInventory = system.GetAllInventory(Owner);
|
||||
|
||||
_menu = this.CreateWindowCenteredLeft<VendingMachineMenu>();
|
||||
_menu.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
_menu.OnItemSelected += OnItemSelected;
|
||||
Refresh();
|
||||
}
|
||||
_menu?.Populate(_cachedInventory, enabled);
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
var enabled = EntMan.TryGetComponent(Owner, out VendingMachineComponent? bendy) && !bendy.Ejecting;
|
||||
public void UpdateAmounts()
|
||||
{
|
||||
var enabled = EntMan.TryGetComponent(Owner, out VendingMachineComponent? bendy) && !bendy.Ejecting;
|
||||
|
||||
var system = EntMan.System<VendingMachineSystem>();
|
||||
_cachedInventory = system.GetAllInventory(Owner);
|
||||
var system = EntMan.System<VendingMachineSystem>();
|
||||
_cachedInventory = system.GetAllInventory(Owner);
|
||||
_menu?.UpdateAmounts(_cachedInventory, enabled);
|
||||
}
|
||||
|
||||
_menu?.Populate(_cachedInventory, enabled);
|
||||
}
|
||||
private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data)
|
||||
{
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
public void UpdateAmounts()
|
||||
{
|
||||
var enabled = EntMan.TryGetComponent(Owner, out VendingMachineComponent? bendy) && !bendy.Ejecting;
|
||||
if (data is not VendorItemsListData { ItemIndex: var itemIndex })
|
||||
return;
|
||||
|
||||
var system = EntMan.System<VendingMachineSystem>();
|
||||
_cachedInventory = system.GetAllInventory(Owner);
|
||||
_menu?.UpdateAmounts(_cachedInventory, enabled);
|
||||
}
|
||||
if (_cachedInventory.Count == 0)
|
||||
return;
|
||||
|
||||
private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data)
|
||||
{
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
var selectedItem = _cachedInventory.ElementAtOrDefault(itemIndex);
|
||||
|
||||
if (data is not VendorItemsListData { ItemIndex: var itemIndex })
|
||||
return;
|
||||
if (selectedItem == null)
|
||||
return;
|
||||
|
||||
if (_cachedInventory.Count == 0)
|
||||
return;
|
||||
SendPredictedMessage(new VendingMachineEjectMessage(selectedItem.Type, selectedItem.ID));
|
||||
}
|
||||
|
||||
var selectedItem = _cachedInventory.ElementAtOrDefault(itemIndex);
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
if (selectedItem == null)
|
||||
return;
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
SendPredictedMessage(new VendingMachineEjectMessage(selectedItem.Type, selectedItem.ID));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
_menu.OnItemSelected -= OnItemSelected;
|
||||
_menu.OnClose -= Close;
|
||||
_menu.Dispose();
|
||||
}
|
||||
_menu.OnItemSelected -= OnItemSelected;
|
||||
_menu.OnClose -= Close;
|
||||
_menu.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,15 +12,7 @@ public sealed partial class VendingMachineSystem : SharedVendingMachineSystem
|
|||
[Dependency] private SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private SpriteSystem _sprite = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<VendingMachineComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
SubscribeLocalEvent<VendingMachineComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeLocalEvent<VendingMachineComponent, ComponentHandleState>(OnVendingHandleState);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnVendingHandleState(Entity<VendingMachineComponent> entity, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not VendingMachineComponentState state)
|
||||
|
|
@ -85,6 +77,7 @@ public sealed partial class VendingMachineSystem : SharedVendingMachineSystem
|
|||
}
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnAnimationCompleted(EntityUid uid, VendingMachineComponent component, AnimationCompletedEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
|
|
@ -99,6 +92,7 @@ public sealed partial class VendingMachineSystem : SharedVendingMachineSystem
|
|||
UpdateAppearance(uid, visualState, component, sprite);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnAppearanceChange(EntityUid uid, VendingMachineComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ using Content.Shared.Storage.EntitySystems;
|
|||
using Content.Shared.VendingMachines;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ public sealed partial class VendingMachineContrabandWireAction : BaseToggleWireA
|
|||
|
||||
public override Color Color { get; set; } = Color.Green;
|
||||
public override string Name { get; set; } = "wire-name-vending-contraband";
|
||||
public override object? StatusKey { get; } = ContrabandWireKey.StatusKey;
|
||||
public override object? TimeoutKey { get; } = ContrabandWireKey.TimeoutKey;
|
||||
public override object StatusKey => ContrabandWireKey.StatusKey;
|
||||
public override object TimeoutKey => ContrabandWireKey.TimeoutKey;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public sealed partial class VendingMachineEjectItemWireAction : ComponentWireAct
|
|||
public override Color Color { get; set; } = Color.Red;
|
||||
public override string Name { get; set; } = "wire-name-vending-eject";
|
||||
|
||||
public override object? StatusKey { get; } = EjectWireKey.StatusKey;
|
||||
public override object StatusKey => EjectWireKey.StatusKey;
|
||||
|
||||
public override StatusLightState? GetLightState(Wire wire, VendingMachineComponent comp)
|
||||
=> comp.CanShoot ? StatusLightState.BlinkingFast : StatusLightState.On;
|
||||
|
|
|
|||
|
|
@ -13,235 +13,226 @@ using Content.Shared.Wall;
|
|||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.VendingMachines
|
||||
namespace Content.Server.VendingMachines;
|
||||
|
||||
public sealed partial class VendingMachineSystem : SharedVendingMachineSystem
|
||||
{
|
||||
public sealed partial class VendingMachineSystem : SharedVendingMachineSystem
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
[Dependency] private PricingSystem _pricing = default!;
|
||||
[Dependency] private ThrowingSystem _throwingSystem = default!;
|
||||
|
||||
private const float WallVendEjectDistanceFromWall = 1f;
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args)
|
||||
{
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
[Dependency] private PricingSystem _pricing = default!;
|
||||
[Dependency] private ThrowingSystem _throwingSystem = default!;
|
||||
var price = 0.0;
|
||||
|
||||
private const float WallVendEjectDistanceFromWall = 1f;
|
||||
|
||||
public override void Initialize()
|
||||
foreach (var entry in component.Inventory.Values)
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<VendingMachineComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, PriceCalculationEvent>(OnVendingPrice);
|
||||
SubscribeLocalEvent<VendingMachineComponent, TryVocalizeEvent>(OnTryVocalize);
|
||||
|
||||
SubscribeLocalEvent<VendingMachineComponent, VendingMachineSelfDispenseEvent>(OnSelfDispense);
|
||||
|
||||
SubscribeLocalEvent<VendingMachineRestockComponent, PriceCalculationEvent>(OnPriceCalculation);
|
||||
}
|
||||
|
||||
private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args)
|
||||
{
|
||||
var price = 0.0;
|
||||
|
||||
foreach (var entry in component.Inventory.Values)
|
||||
if (!ProtoMan.TryIndex<EntityPrototype>(entry.ID, out var proto))
|
||||
{
|
||||
if (!ProtoMan.TryIndex<EntityPrototype>(entry.ID, out var proto))
|
||||
{
|
||||
Log.Error($"Unable to find entity prototype {entry.ID} on {ToPrettyString(uid)} vending.");
|
||||
continue;
|
||||
}
|
||||
|
||||
price += entry.Amount * _pricing.GetEstimatedPrice(proto);
|
||||
Log.Error($"Unable to find entity prototype {entry.ID} on {ToPrettyString(uid)} vending.");
|
||||
continue;
|
||||
}
|
||||
|
||||
args.Price += price;
|
||||
price += entry.Amount * _pricing.GetEstimatedPrice(proto);
|
||||
}
|
||||
|
||||
protected override void OnMapInit(EntityUid uid, VendingMachineComponent component, MapInitEvent args)
|
||||
{
|
||||
base.OnMapInit(uid, component, args);
|
||||
args.Price += price;
|
||||
}
|
||||
|
||||
if (HasComp<ApcPowerReceiverComponent>(uid))
|
||||
{
|
||||
TryUpdateVisualState((uid, component));
|
||||
}
|
||||
}
|
||||
protected override void OnMapInit(EntityUid uid, VendingMachineComponent component, MapInitEvent args)
|
||||
{
|
||||
base.OnMapInit(uid, component, args);
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, VendingMachineComponent component, ref PowerChangedEvent args)
|
||||
if (HasComp<ApcPowerReceiverComponent>(uid))
|
||||
{
|
||||
TryUpdateVisualState((uid, component));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDamageChanged(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
|
||||
[SubscribeLocalEvent]
|
||||
private void OnPowerChanged(EntityUid uid, VendingMachineComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
TryUpdateVisualState((uid, component));
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnDamageChanged(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (!args.DamageIncreased && component.Broken)
|
||||
{
|
||||
if (!args.DamageIncreased && component.Broken)
|
||||
{
|
||||
component.Broken = false;
|
||||
Dirty(uid, component);
|
||||
TryUpdateVisualState((uid, component));
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.Broken || component.DispenseOnHitCoolingDown ||
|
||||
component.DispenseOnHitChance == null || args.DamageDelta == null)
|
||||
return;
|
||||
|
||||
if (args.DamageIncreased && args.DamageDelta.GetTotal() >= component.DispenseOnHitThreshold &&
|
||||
_random.Prob(component.DispenseOnHitChance.Value))
|
||||
{
|
||||
if (component.DispenseOnHitCooldown != null)
|
||||
{
|
||||
component.DispenseOnHitEnd = Timing.CurTime + component.DispenseOnHitCooldown.Value;
|
||||
}
|
||||
|
||||
EjectRandom(uid, throwItem: true, forceEject: true, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelfDispense(EntityUid uid, VendingMachineComponent component, VendingMachineSelfDispenseEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="VendingMachineComponent.Contraband"/> property of the vending machine.
|
||||
/// </summary>
|
||||
public void SetContraband(EntityUid uid, bool contraband, VendingMachineComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.Contraband = contraband;
|
||||
component.Broken = false;
|
||||
Dirty(uid, component);
|
||||
TryUpdateVisualState((uid, component));
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects a random item from the available stock. Will do nothing if the vending machine is empty.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <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>
|
||||
/// <param name="vendComponent"></param>
|
||||
public void EjectRandom(EntityUid uid, bool throwItem, bool forceEject = false, VendingMachineComponent? vendComponent = null)
|
||||
if (component.Broken || component.DispenseOnHitCoolingDown ||
|
||||
component.DispenseOnHitChance == null || args.DamageDelta == null)
|
||||
return;
|
||||
|
||||
if (args.DamageIncreased && args.DamageDelta.GetTotal() >= component.DispenseOnHitThreshold &&
|
||||
_random.Prob(component.DispenseOnHitChance.Value))
|
||||
{
|
||||
if (!Resolve(uid, ref vendComponent))
|
||||
return;
|
||||
|
||||
var availableItems = GetAvailableInventory(uid, vendComponent);
|
||||
if (availableItems.Count <= 0)
|
||||
return;
|
||||
|
||||
var item = _random.Pick(availableItems);
|
||||
|
||||
if (forceEject)
|
||||
if (component.DispenseOnHitCooldown != null)
|
||||
{
|
||||
vendComponent.NextItemToEject = item.ID;
|
||||
vendComponent.ThrowNextItem = throwItem;
|
||||
var entry = GetEntry(uid, item.ID, item.Type, vendComponent);
|
||||
if (entry != null)
|
||||
entry.Amount--;
|
||||
EjectItem(uid, vendComponent, forceEject);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryEjectVendorItem(uid, item.Type, item.ID, throwItem, user: null, vendComponent: vendComponent);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void EjectItem(EntityUid uid, VendingMachineComponent? vendComponent = null, bool forceEject = false)
|
||||
{
|
||||
if (!Resolve(uid, ref vendComponent))
|
||||
return;
|
||||
|
||||
// No need to update the visual state because we never changed it during a forced eject
|
||||
if (!forceEject)
|
||||
TryUpdateVisualState((uid, vendComponent));
|
||||
|
||||
if (string.IsNullOrEmpty(vendComponent.NextItemToEject))
|
||||
{
|
||||
vendComponent.ThrowNextItem = false;
|
||||
return;
|
||||
component.DispenseOnHitEnd = Timing.CurTime + component.DispenseOnHitCooldown.Value;
|
||||
}
|
||||
|
||||
// Default spawn coordinates
|
||||
var xform = Transform(uid);
|
||||
var spawnCoordinates = xform.Coordinates;
|
||||
|
||||
//Make sure the wallvends spawn outside of the wall.
|
||||
if (TryComp<WallMountComponent>(uid, out var wallMountComponent))
|
||||
{
|
||||
var offset = (wallMountComponent.Direction + xform.LocalRotation - Math.PI / 2).ToVec() * WallVendEjectDistanceFromWall;
|
||||
spawnCoordinates = spawnCoordinates.Offset(offset);
|
||||
}
|
||||
|
||||
var ent = Spawn(vendComponent.NextItemToEject, spawnCoordinates);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var disabled = EntityQueryEnumerator<EmpDisabledComponent, VendingMachineComponent>();
|
||||
while (disabled.MoveNext(out var uid, out _, out var comp))
|
||||
{
|
||||
if (comp.NextEmpEject < Timing.CurTime)
|
||||
{
|
||||
EjectRandom(uid, true, false, comp);
|
||||
comp.NextEmpEject += (5 * comp.EjectDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPriceCalculation(EntityUid uid, VendingMachineRestockComponent component, ref PriceCalculationEvent args)
|
||||
{
|
||||
List<double> priceSets = new();
|
||||
|
||||
// Find the most expensive inventory and use that as the highest price.
|
||||
foreach (var vendingInventory in component.CanRestock)
|
||||
{
|
||||
double total = 0;
|
||||
|
||||
if (ProtoMan.TryIndex(vendingInventory, out VendingMachineInventoryPrototype? inventoryPrototype))
|
||||
{
|
||||
foreach (var (item, amount) in inventoryPrototype.StartingInventory)
|
||||
{
|
||||
if (ProtoMan.TryIndex(item, out EntityPrototype? entity))
|
||||
total += _pricing.GetEstimatedPrice(entity) * amount;
|
||||
}
|
||||
}
|
||||
|
||||
priceSets.Add(total);
|
||||
}
|
||||
|
||||
args.Price += priceSets.Max();
|
||||
}
|
||||
|
||||
private void OnTryVocalize(Entity<VendingMachineComponent> ent, ref TryVocalizeEvent args)
|
||||
{
|
||||
args.Cancelled |= ent.Comp.Broken;
|
||||
EjectRandom(uid, throwItem: true, forceEject: true, component);
|
||||
}
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnSelfDispense(EntityUid uid, VendingMachineComponent component, VendingMachineSelfDispenseEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="VendingMachineComponent.Contraband"/> property of the vending machine.
|
||||
/// </summary>
|
||||
public void SetContraband(EntityUid uid, bool contraband, VendingMachineComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.Contraband = contraband;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects a random item from the available stock. Will do nothing if the vending machine is empty.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <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>
|
||||
/// <param name="vendComponent"></param>
|
||||
public void EjectRandom(EntityUid uid, bool throwItem, bool forceEject = false, VendingMachineComponent? vendComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref vendComponent))
|
||||
return;
|
||||
|
||||
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(uid, item.ID, item.Type, vendComponent);
|
||||
if (entry != null)
|
||||
entry.Amount--;
|
||||
EjectItem(uid, vendComponent, forceEject);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryEjectVendorItem(uid, item.Type, item.ID, throwItem, user: null, vendComponent: vendComponent);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void EjectItem(EntityUid uid, VendingMachineComponent? vendComponent = null, bool forceEject = false)
|
||||
{
|
||||
if (!Resolve(uid, ref vendComponent))
|
||||
return;
|
||||
|
||||
// No need to update the visual state because we never changed it during a forced eject
|
||||
if (!forceEject)
|
||||
TryUpdateVisualState((uid, vendComponent));
|
||||
|
||||
if (string.IsNullOrEmpty(vendComponent.NextItemToEject))
|
||||
{
|
||||
vendComponent.ThrowNextItem = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Default spawn coordinates
|
||||
var xform = Transform(uid);
|
||||
var spawnCoordinates = xform.Coordinates;
|
||||
|
||||
//Make sure the wallvends spawn outside of the wall.
|
||||
if (TryComp<WallMountComponent>(uid, out var wallMountComponent))
|
||||
{
|
||||
var offset = (wallMountComponent.Direction + xform.LocalRotation - Math.PI / 2).ToVec() * WallVendEjectDistanceFromWall;
|
||||
spawnCoordinates = spawnCoordinates.Offset(offset);
|
||||
}
|
||||
|
||||
var ent = Spawn(vendComponent.NextItemToEject, spawnCoordinates);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var disabled = EntityQueryEnumerator<EmpDisabledComponent, VendingMachineComponent>();
|
||||
while (disabled.MoveNext(out var uid, out _, out var comp))
|
||||
{
|
||||
if (comp.NextEmpEject < Timing.CurTime)
|
||||
{
|
||||
EjectRandom(uid, true, false, comp);
|
||||
comp.NextEmpEject += (5 * comp.EjectDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnPriceCalculation(EntityUid uid, VendingMachineRestockComponent component, ref PriceCalculationEvent args)
|
||||
{
|
||||
List<double> priceSets = new();
|
||||
|
||||
// Find the most expensive inventory and use that as the highest price.
|
||||
foreach (var vendingInventory in component.CanRestock)
|
||||
{
|
||||
double total = 0;
|
||||
|
||||
if (ProtoMan.TryIndex(vendingInventory, out VendingMachineInventoryPrototype? inventoryPrototype))
|
||||
{
|
||||
foreach (var (item, amount) in inventoryPrototype.StartingInventory)
|
||||
{
|
||||
if (ProtoMan.TryIndex(item, out EntityPrototype? entity))
|
||||
total += _pricing.GetEstimatedPrice(entity) * amount;
|
||||
}
|
||||
}
|
||||
|
||||
priceSets.Add(total);
|
||||
}
|
||||
|
||||
args.Price += priceSets.Max();
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnTryVocalize(Entity<VendingMachineComponent> ent, ref TryVocalizeEvent args)
|
||||
{
|
||||
args.Cancelled |= ent.Comp.Broken;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ public abstract partial class SharedVendingMachineSystem
|
|||
TryUpdateVisualState((uid, vendComponent));
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnAfterInteract(EntityUid uid, VendingMachineRestockComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Target is not { } target || !args.CanReach || args.Handled)
|
||||
|
|
@ -102,6 +103,7 @@ public abstract partial class SharedVendingMachineSystem
|
|||
machineComponent.RestockStream = Audio.PlayPredicted(component.SoundRestockStart, target, args.User)?.Entity;
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnRestockDoAfter(Entity<VendingMachineComponent> ent, ref RestockDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using Content.Shared.DoAfter;
|
|||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Emp;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.UserInterface;
|
||||
|
|
@ -39,15 +38,6 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
|
|||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<VendingMachineComponent, ComponentGetState>(OnVendingGetState);
|
||||
SubscribeLocalEvent<VendingMachineComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<VendingMachineComponent, GotEmaggedEvent>(OnEmagged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, EmpPulseEvent>(OnEmpPulse);
|
||||
SubscribeLocalEvent<VendingMachineComponent, RestockDoAfterEvent>(OnRestockDoAfter);
|
||||
SubscribeLocalEvent<VendingMachineComponent, ActivatableUIOpenAttemptEvent>(OnActivatableUIOpenAttempt);
|
||||
SubscribeLocalEvent<VendingMachineComponent, BreakageEventArgs>(OnBreak);
|
||||
|
||||
SubscribeLocalEvent<VendingMachineRestockComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
|
||||
Subs.BuiEvents<VendingMachineComponent>(VendingMachineUiKey.Key, subs =>
|
||||
{
|
||||
|
|
@ -55,6 +45,7 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
|
|||
});
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnVendingGetState(Entity<VendingMachineComponent> entity, ref ComponentGetState args)
|
||||
{
|
||||
var component = entity.Comp;
|
||||
|
|
@ -145,11 +136,13 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
|
|||
AuthorizedVend(entity.Owner, actor, args.Type, args.ID, entity.Comp);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
protected virtual void OnMapInit(EntityUid uid, VendingMachineComponent component, MapInitEvent args)
|
||||
{
|
||||
RestockInventoryFromPrototype(uid, component, component.InitialStockQuality);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnEmpPulse(Entity<VendingMachineComponent> ent, ref EmpPulseEvent args)
|
||||
{
|
||||
if (!ent.Comp.Broken && _receiver.IsPowered(ent.Owner))
|
||||
|
|
@ -206,6 +199,7 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
|
|||
/// <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>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="vendComponent"></param>
|
||||
public void TryEjectVendorItem(EntityUid uid, InventoryType type, string itemId, bool throwItem, EntityUid? user = null, VendingMachineComponent? vendComponent = null)
|
||||
{
|
||||
|
|
@ -333,6 +327,7 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
|
|||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnEmagged(EntityUid uid, VendingMachineComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
|
|
@ -429,12 +424,14 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnActivatableUIOpenAttempt(EntityUid uid, VendingMachineComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
if (component.Broken)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnBreak(EntityUid uid, VendingMachineComponent vendComponent, BreakageEventArgs eventArgs)
|
||||
{
|
||||
vendComponent.Broken = true;
|
||||
|
|
|
|||
|
|
@ -5,303 +5,299 @@ using Robust.Shared.Serialization;
|
|||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.VendingMachines
|
||||
namespace Content.Shared.VendingMachines;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause]
|
||||
public sealed partial class VendingMachineComponent : Component
|
||||
{
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause]
|
||||
public sealed partial class VendingMachineComponent : Component
|
||||
/// <summary>
|
||||
/// PrototypeID for the vending machine's inventory, see <see cref="VendingMachineInventoryPrototype"/>
|
||||
/// </summary>
|
||||
// Okay so not using ProtoId here is load-bearing because the ProtoId serializer will log errors if the prototype doesn't exist.
|
||||
[DataField("pack", customTypeSerializer: typeof(PrototypeIdSerializer<VendingMachineInventoryPrototype>), required: true)]
|
||||
public string PackPrototypeId = string.Empty;
|
||||
|
||||
/// <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]
|
||||
public TimeSpan DenyDelay = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <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]
|
||||
public TimeSpan EjectDelay = TimeSpan.FromSeconds(1.2);
|
||||
|
||||
[DataField]
|
||||
public Dictionary<string, VendingMachineInventoryEntry> Inventory = new();
|
||||
|
||||
[DataField]
|
||||
public Dictionary<string, VendingMachineInventoryEntry> EmaggedInventory = new();
|
||||
|
||||
[DataField]
|
||||
public Dictionary<string, VendingMachineInventoryEntry> ContrabandInventory = new();
|
||||
|
||||
/// <summary>
|
||||
/// If true then unlocks the <see cref="ContrabandInventory"/>
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Contraband;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Ejecting => EjectEnd != null;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Denying => DenyEnd != null;
|
||||
|
||||
[ViewVariables]
|
||||
public bool DispenseOnHitCoolingDown => DispenseOnHitEnd != null;
|
||||
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan? EjectEnd;
|
||||
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan? DenyEnd;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan? DispenseOnHitEnd;
|
||||
|
||||
public string? NextItemToEject;
|
||||
|
||||
[DataField]
|
||||
public bool Broken;
|
||||
|
||||
/// <summary>
|
||||
/// When true, will forcefully throw any object it dispenses
|
||||
/// </summary>
|
||||
[DataField]
|
||||
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.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
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]
|
||||
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="VendingMachineComponent.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]
|
||||
public TimeSpan? DispenseOnHitCooldown = TimeSpan.FromSeconds(1.0);
|
||||
|
||||
/// <summary>
|
||||
/// Sound that plays when ejecting an item
|
||||
/// </summary>
|
||||
[DataField]
|
||||
// Grabbed from: https://github.com/tgstation/tgstation/blob/d34047a5ae911735e35cd44a210953c9563caa22/sound/machines/machine_vend.ogg
|
||||
public SoundSpecifier SoundVend = new SoundPathSpecifier("/Audio/Machines/machine_vend.ogg")
|
||||
{
|
||||
/// <summary>
|
||||
/// PrototypeID for the vending machine's inventory, see <see cref="VendingMachineInventoryPrototype"/>
|
||||
/// </summary>
|
||||
// Okay so not using ProtoId here is load-bearing because the ProtoId serializer will log errors if the prototype doesn't exist.
|
||||
[DataField("pack", customTypeSerializer: typeof(PrototypeIdSerializer<VendingMachineInventoryPrototype>), required: true)]
|
||||
public string PackPrototypeId = string.Empty;
|
||||
|
||||
/// <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]
|
||||
public TimeSpan DenyDelay = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <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]
|
||||
public TimeSpan EjectDelay = TimeSpan.FromSeconds(1.2);
|
||||
|
||||
[DataField]
|
||||
public Dictionary<string, VendingMachineInventoryEntry> Inventory = new();
|
||||
|
||||
[DataField]
|
||||
public Dictionary<string, VendingMachineInventoryEntry> EmaggedInventory = new();
|
||||
|
||||
[DataField]
|
||||
public Dictionary<string, VendingMachineInventoryEntry> ContrabandInventory = new();
|
||||
|
||||
/// <summary>
|
||||
/// If true then unlocks the <see cref="ContrabandInventory"/>
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Contraband;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Ejecting => EjectEnd != null;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Denying => DenyEnd != null;
|
||||
|
||||
[ViewVariables]
|
||||
public bool DispenseOnHitCoolingDown => DispenseOnHitEnd != null;
|
||||
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan? EjectEnd;
|
||||
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan? DenyEnd;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan? DispenseOnHitEnd;
|
||||
|
||||
public string? NextItemToEject;
|
||||
|
||||
[DataField]
|
||||
public bool Broken;
|
||||
|
||||
/// <summary>
|
||||
/// When true, will forcefully throw any object it dispenses
|
||||
/// </summary>
|
||||
[DataField]
|
||||
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.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
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]
|
||||
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="VendingMachineComponent.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]
|
||||
public TimeSpan? DispenseOnHitCooldown = TimeSpan.FromSeconds(1.0);
|
||||
|
||||
/// <summary>
|
||||
/// Sound that plays when ejecting an item
|
||||
/// </summary>
|
||||
[DataField]
|
||||
// Grabbed from: https://github.com/tgstation/tgstation/blob/d34047a5ae911735e35cd44a210953c9563caa22/sound/machines/machine_vend.ogg
|
||||
public SoundSpecifier SoundVend = new SoundPathSpecifier("/Audio/Machines/machine_vend.ogg")
|
||||
Params = new AudioParams
|
||||
{
|
||||
Params = new AudioParams
|
||||
{
|
||||
Volume = -4f,
|
||||
Variation = 0.15f
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Sound that plays when an item can't be ejected
|
||||
/// </summary>
|
||||
[DataField]
|
||||
// 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");
|
||||
|
||||
public float NonLimitedEjectForce = 7.5f;
|
||||
|
||||
public float NonLimitedEjectRange = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// The quality of the stock in the vending machine on spawn.
|
||||
/// Represents the percentage chance (0.0f = 0%, 1.0f = 100%) each set of items in the machine is fully-stocked.
|
||||
/// If not fully stocked, the stock will have a random value between 0 (inclusive) and max stock (exclusive).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float InitialStockQuality = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// While disabled by EMP it randomly ejects items
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextEmpEject = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Audio entity used during restock in case the doafter gets canceled.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? RestockStream;
|
||||
|
||||
#region Client Visuals
|
||||
/// <summary>
|
||||
/// RSI state for when the vending machine is unpowered.
|
||||
/// Will be displayed on the layer <see cref="VendingMachineVisualLayers.Base"/>
|
||||
/// </summary>
|
||||
[DataField]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
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="VendingMachineComponent.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;
|
||||
#endregion
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable, DataDefinition]
|
||||
public sealed partial class VendingMachineInventoryEntry
|
||||
{
|
||||
[DataField]
|
||||
public InventoryType Type;
|
||||
|
||||
[DataField]
|
||||
public string ID;
|
||||
|
||||
[DataField]
|
||||
public uint Amount;
|
||||
|
||||
public VendingMachineInventoryEntry(InventoryType type, string id, uint amount)
|
||||
{
|
||||
Type = type;
|
||||
ID = id;
|
||||
Amount = amount;
|
||||
Volume = -4f,
|
||||
Variation = 0.15f
|
||||
}
|
||||
|
||||
public VendingMachineInventoryEntry(VendingMachineInventoryEntry entry)
|
||||
{
|
||||
Type = entry.Type;
|
||||
ID = entry.ID;
|
||||
Amount = entry.Amount;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum InventoryType : byte
|
||||
{
|
||||
Regular,
|
||||
Emagged,
|
||||
Contraband
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineVisuals : byte
|
||||
{
|
||||
VisualState
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineVisualState : byte
|
||||
{
|
||||
Normal,
|
||||
Off,
|
||||
Broken,
|
||||
Eject,
|
||||
Deny,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ContrabandWireKey : byte
|
||||
{
|
||||
StatusKey,
|
||||
TimeoutKey
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum EjectWireKey : byte
|
||||
{
|
||||
StatusKey,
|
||||
}
|
||||
|
||||
public sealed partial class VendingMachineSelfDispenseEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class VendingMachineComponentState : ComponentState
|
||||
/// <summary>
|
||||
/// Sound that plays when an item can't be ejected
|
||||
/// </summary>
|
||||
[DataField]
|
||||
// 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");
|
||||
|
||||
public float NonLimitedEjectForce = 7.5f;
|
||||
|
||||
public float NonLimitedEjectRange = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// The quality of the stock in the vending machine on spawn.
|
||||
/// Represents the percentage chance (0.0f = 0%, 1.0f = 100%) each set of items in the machine is fully-stocked.
|
||||
/// If not fully stocked, the stock will have a random value between 0 (inclusive) and max stock (exclusive).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float InitialStockQuality = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// While disabled by EMP it randomly ejects items
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextEmpEject = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Audio entity used during restock in case the doafter gets canceled.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? RestockStream;
|
||||
|
||||
#region Client Visuals
|
||||
/// <summary>
|
||||
/// RSI state for when the vending machine is unpowered.
|
||||
/// Will be displayed on the layer <see cref="VendingMachineVisualLayers.Base"/>
|
||||
/// </summary>
|
||||
[DataField]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
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="VendingMachineComponent.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;
|
||||
#endregion
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable, DataDefinition]
|
||||
public sealed partial class VendingMachineInventoryEntry
|
||||
{
|
||||
[DataField]
|
||||
public InventoryType Type;
|
||||
|
||||
[DataField]
|
||||
public string ID;
|
||||
|
||||
[DataField]
|
||||
public uint Amount;
|
||||
|
||||
public VendingMachineInventoryEntry(InventoryType type, string id, uint amount)
|
||||
{
|
||||
public Dictionary<string, VendingMachineInventoryEntry> Inventory = new();
|
||||
Type = type;
|
||||
ID = id;
|
||||
Amount = amount;
|
||||
}
|
||||
|
||||
public Dictionary<string, VendingMachineInventoryEntry> EmaggedInventory = new();
|
||||
|
||||
public Dictionary<string, VendingMachineInventoryEntry> ContrabandInventory = new();
|
||||
|
||||
public bool Contraband;
|
||||
|
||||
public TimeSpan? EjectEnd;
|
||||
|
||||
public TimeSpan? DenyEnd;
|
||||
|
||||
public TimeSpan? DispenseOnHitEnd;
|
||||
|
||||
public bool Broken;
|
||||
public VendingMachineInventoryEntry(VendingMachineInventoryEntry entry)
|
||||
{
|
||||
Type = entry.Type;
|
||||
ID = entry.ID;
|
||||
Amount = entry.Amount;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum InventoryType : byte
|
||||
{
|
||||
Regular,
|
||||
Emagged,
|
||||
Contraband
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineVisuals : byte
|
||||
{
|
||||
VisualState
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineVisualState : byte
|
||||
{
|
||||
Normal,
|
||||
Off,
|
||||
Broken,
|
||||
Eject,
|
||||
Deny
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ContrabandWireKey : byte
|
||||
{
|
||||
StatusKey,
|
||||
TimeoutKey
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum EjectWireKey : byte
|
||||
{
|
||||
StatusKey
|
||||
}
|
||||
|
||||
public sealed partial class VendingMachineSelfDispenseEvent : InstantActionEvent;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class VendingMachineComponentState : ComponentState
|
||||
{
|
||||
public Dictionary<string, VendingMachineInventoryEntry> Inventory = new();
|
||||
|
||||
public Dictionary<string, VendingMachineInventoryEntry> EmaggedInventory = new();
|
||||
|
||||
public Dictionary<string, VendingMachineInventoryEntry> ContrabandInventory = new();
|
||||
|
||||
public bool Contraband;
|
||||
|
||||
public TimeSpan? EjectEnd;
|
||||
|
||||
public TimeSpan? DenyEnd;
|
||||
|
||||
public TimeSpan? DispenseOnHitEnd;
|
||||
|
||||
public bool Broken;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,16 @@
|
|||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.VendingMachines
|
||||
{
|
||||
[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;
|
||||
}
|
||||
}
|
||||
namespace Content.Shared.VendingMachines;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineUiKey
|
||||
{
|
||||
Key,
|
||||
}
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class VendingMachineEjectMessage(InventoryType type, string id) : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly InventoryType Type = type;
|
||||
public readonly string ID = id;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VendingMachineUiKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Shared.VendingMachines
|
||||
namespace Content.Shared.VendingMachines;
|
||||
|
||||
[Prototype]
|
||||
public sealed partial class VendingMachineInventoryPrototype : IPrototype
|
||||
{
|
||||
[Prototype]
|
||||
public sealed partial class VendingMachineInventoryPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("startingInventory", customTypeSerializer:typeof(PrototypeIdDictionarySerializer<uint, EntityPrototype>))]
|
||||
public Dictionary<string, uint> StartingInventory { get; private set; } = new();
|
||||
[DataField(customTypeSerializer:typeof(PrototypeIdDictionarySerializer<uint, EntityPrototype>))]
|
||||
public Dictionary<string, uint> StartingInventory { get; private set; } = new();
|
||||
|
||||
[DataField("emaggedInventory", customTypeSerializer:typeof(PrototypeIdDictionarySerializer<uint, EntityPrototype>))]
|
||||
public Dictionary<string, uint>? EmaggedInventory { get; private set; }
|
||||
[DataField(customTypeSerializer:typeof(PrototypeIdDictionarySerializer<uint, EntityPrototype>))]
|
||||
public Dictionary<string, uint>? EmaggedInventory { get; private set; }
|
||||
|
||||
[DataField("contrabandInventory", customTypeSerializer:typeof(PrototypeIdDictionarySerializer<uint, EntityPrototype>))]
|
||||
public Dictionary<string, uint>? ContrabandInventory { get; private set; }
|
||||
}
|
||||
[DataField(customTypeSerializer:typeof(PrototypeIdDictionarySerializer<uint, EntityPrototype>))]
|
||||
public Dictionary<string, uint>? ContrabandInventory { get; private set; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue