diff --git a/Content.Client/SS220/SmartFridge/SmartFridgeBoundUserInterface.cs b/Content.Client/SS220/SmartFridge/SmartFridgeBoundUserInterface.cs new file mode 100644 index 000000000000..f5e84bcb92ba --- /dev/null +++ b/Content.Client/SS220/SmartFridge/SmartFridgeBoundUserInterface.cs @@ -0,0 +1,84 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Content.Client.SS220.SmartFridge.UI; +using Content.Shared.SS220.SmartFridge; +using Content.Shared.VendingMachines; +using Robust.Client.UserInterface.Controls; +using System.Linq; + +namespace Content.Client.SS220.SmartFridge +{ + public sealed class SmartFridgeBoundUserInterface : BoundUserInterface + { + [ViewVariables] + private SmartFridgeMenu? _menu; + + [ViewVariables] + private List _cachedInventory = new(); + + [ViewVariables] + private List _cachedFilteredIndex = new(); + + public SmartFridgeBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _menu = new SmartFridgeMenu { Title = EntMan.GetComponent(Owner).EntityName }; + + _menu.OnClose += Close; + _menu.OnItemSelected += OnItemSelected; + _menu.OnSearchChanged += OnSearchChanged; + + UpdateUI(); + + _menu.OpenCentered(); + } + + private void OnItemSelected(ItemList.ItemListSelectedEventArgs args) + { + + if (_cachedInventory.Count == 0) + return; + + var selectedItem = _cachedInventory.ElementAtOrDefault(_cachedFilteredIndex.ElementAtOrDefault(args.ItemIndex)); + + if (selectedItem == null) + return; + + SendPredictedMessage(new SmartFridgeInteractWithItemEvent(selectedItem.EntityUids[0])); + + UpdateUI(); + } + + public void UpdateUI() + { + var smartFridgeSys = EntMan.System(); + _cachedInventory = smartFridgeSys.GetAllInventory(Owner); + _menu?.Populate(_cachedInventory, out _cachedFilteredIndex); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + if (_menu == null) + return; + + _menu.OnItemSelected -= OnItemSelected; + _menu.OnClose -= Close; + _menu.Dispose(); + } + + private void OnSearchChanged(string? filter) + { + _menu?.Populate(_cachedInventory, out _cachedFilteredIndex, filter); + } + } + +} diff --git a/Content.Client/SS220/SmartFridge/SmartFridgeSystem.cs b/Content.Client/SS220/SmartFridge/SmartFridgeSystem.cs new file mode 100644 index 000000000000..fe03204b4d57 --- /dev/null +++ b/Content.Client/SS220/SmartFridge/SmartFridgeSystem.cs @@ -0,0 +1,136 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Content.Shared.SS220.SmartFridge; +using Robust.Client.Animations; +using Robust.Client.GameObjects; + +namespace Content.Client.SS220.SmartFridge; + +public sealed class SmartFridgeSystem : SharedSmartFridgeSystem +{ + [Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAppearanceChange); + SubscribeLocalEvent(OnAnimationCompleted); + } + + private void OnAnimationCompleted(EntityUid uid, SmartFridgeComponent component, AnimationCompletedEvent args) + { + if (!TryComp(uid, out var sprite)) + return; + + if (!TryComp(uid, out var appearance) || + !_appearanceSystem.TryGetData(uid, SmartFridgeVisuals.VisualState, out var visualState, appearance)) + { + visualState = SmartFridgeVisualState.Normal; + } + + UpdateAppearance(uid, visualState, component, sprite); + } + + private void OnAppearanceChange(EntityUid uid, SmartFridgeComponent component, ref AppearanceChangeEvent args) + { + if (args.Sprite == null) + return; + + if (!args.AppearanceData.TryGetValue(SmartFridgeVisuals.VisualState, out var visualStateObject) || + visualStateObject is not SmartFridgeVisualState visualState) + { + visualState = SmartFridgeVisualState.Normal; + } + + UpdateAppearance(uid, visualState, component, args.Sprite); + } + + private void UpdateAppearance(EntityUid uid, SmartFridgeVisualState visualState, SmartFridgeComponent component, SpriteComponent sprite) + { + SetLayerState(SmartFridgeVisualLayers.Base, component.OffState, sprite); + + switch (visualState) + { + case SmartFridgeVisualState.Normal: + SetLayerState(SmartFridgeVisualLayers.BaseUnshaded, component.NormalState, sprite); + SetLayerState(SmartFridgeVisualLayers.Screen, component.ScreenState, sprite); + break; + + case SmartFridgeVisualState.Deny: + if (component.LoopDenyAnimation) + SetLayerState(SmartFridgeVisualLayers.BaseUnshaded, component.DenyState, sprite); + else + PlayAnimation(uid, SmartFridgeVisualLayers.BaseUnshaded, component.DenyState, component.DenyDelay, sprite); + + SetLayerState(SmartFridgeVisualLayers.Screen, component.ScreenState, sprite); + break; + + case SmartFridgeVisualState.Broken: + HideLayers(sprite); + SetLayerState(SmartFridgeVisualLayers.Base, component.BrokenState, sprite); + break; + + case SmartFridgeVisualState.Off: + HideLayers(sprite); + break; + } + } + + private static void SetLayerState(SmartFridgeVisualLayers 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, SmartFridgeVisualLayers 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(SmartFridgeVisualLayers 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(SmartFridgeVisualLayers.BaseUnshaded, sprite); + HideLayer(SmartFridgeVisualLayers.Screen, sprite); + } + + private static void HideLayer(SmartFridgeVisualLayers layer, SpriteComponent sprite) + { + if (!sprite.LayerMapTryGet(layer, out var actualLayer)) + return; + + sprite.LayerSetVisible(actualLayer, false); + } +} diff --git a/Content.Client/SS220/SmartFridge/UI/SmartFridgeMenu.xaml b/Content.Client/SS220/SmartFridge/UI/SmartFridgeMenu.xaml new file mode 100644 index 000000000000..51c52e710d68 --- /dev/null +++ b/Content.Client/SS220/SmartFridge/UI/SmartFridgeMenu.xaml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/Content.Client/SS220/SmartFridge/UI/SmartFridgeMenu.xaml.cs b/Content.Client/SS220/SmartFridge/UI/SmartFridgeMenu.xaml.cs new file mode 100644 index 000000000000..c1696a5de0b7 --- /dev/null +++ b/Content.Client/SS220/SmartFridge/UI/SmartFridgeMenu.xaml.cs @@ -0,0 +1,112 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using System.Numerics; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using Content.Shared.VendingMachines; + +namespace Content.Client.SS220.SmartFridge.UI +{ + [GenerateTypedNameReferences] + public sealed partial class SmartFridgeMenu : DefaultWindow + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public event Action? OnItemSelected; + public event Action? OnSearchChanged; + + public SmartFridgeMenu() + { + MinSize = new Vector2(250, 150); // Corvax-Resize + SetSize = new Vector2(450, 150); // Corvax-Resize + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + SearchBar.OnTextChanged += _ => + { + OnSearchChanged?.Invoke(SearchBar.Text); + }; + + SmartFridgeContents.OnItemSelected += args => + { + OnItemSelected?.Invoke(args); + }; + } + + /// + /// Populates the list of available items on the vending machine interface + /// and sets icons based on their prototypes + /// + public void Populate(List inventory, out List filteredInventory, string? filter = null) + { + + filteredInventory = new(); + + if (inventory.Count == 0) + { + SmartFridgeContents.Clear(); + var outOfStockText = Loc.GetString("vending-machine-component-try-eject-out-of-stock"); + SmartFridgeContents.AddItem(outOfStockText); + SetSizeAfterUpdate(outOfStockText.Length, SmartFridgeContents.Count); + return; + } + + while (inventory.Count != SmartFridgeContents.Count) + { + if (inventory.Count > SmartFridgeContents.Count) + SmartFridgeContents.AddItem(string.Empty); + else + SmartFridgeContents.RemoveAt(SmartFridgeContents.Count - 1); + } + + var longestEntry = string.Empty; + var spriteSystem = IoCManager.Resolve().GetEntitySystem(); + + var filterCount = 0; + for (var i = 0; i < inventory.Count; i++) + { + var entry = inventory[i]; + var smartFridgeItem = SmartFridgeContents[i - filterCount]; + smartFridgeItem.Text = string.Empty; + smartFridgeItem.Icon = null; + + var itemName = entry.ID; + Texture? icon = null; + if (_prototypeManager.TryIndex(entry.ID, out var prototype)) + { + itemName = prototype.Name; + icon = spriteSystem.GetPrototypeIcon(prototype).Default; + } + + // search filter + if (!string.IsNullOrEmpty(filter) && + !itemName.ToLowerInvariant().Contains(filter.Trim().ToLowerInvariant())) + { + SmartFridgeContents.Remove(smartFridgeItem); + filterCount++; + continue; + } + + if (itemName.Length > longestEntry.Length) + longestEntry = itemName; + + smartFridgeItem.Text = $"{itemName} [{entry.Amount}]"; + smartFridgeItem.Icon = icon; + filteredInventory.Add(i); + } + + SetSizeAfterUpdate(longestEntry.Length, inventory.Count); + } + + private void SetSizeAfterUpdate(int longestEntryLength, int contentCount) + { + SetSize = new Vector2(Math.Clamp((longestEntryLength + 2) * 12, 250, 300), + Math.Clamp(contentCount * 50, 150, 350)); + } + } +} diff --git a/Content.Server/SS220/SmartFridge/SmartFridgeSystem.cs b/Content.Server/SS220/SmartFridge/SmartFridgeSystem.cs new file mode 100644 index 000000000000..ad858840d6a7 --- /dev/null +++ b/Content.Server/SS220/SmartFridge/SmartFridgeSystem.cs @@ -0,0 +1,82 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Content.Shared.SS220.SmartFridge; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Server.UserInterface; +using Content.Shared.Destructible; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; + +namespace Content.Server.SS220.SmartFridge +{ + public sealed class SmartFridgeSystem : SharedSmartFridgeSystem + { + [Dependency] private readonly AppearanceSystem _appearanceSystem = default!; + + private ISawmill _sawmill = default!; + + public override void Initialize() + { + base.Initialize(); + + _sawmill = Logger.GetSawmill("smartfridge"); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnActivatableUIOpenAttempt); + SubscribeLocalEvent(OnBreak); + + } + private void OnActivatableUIOpenAttempt(EntityUid uid, SmartFridgeComponent component, ActivatableUIOpenAttemptEvent args) + { + if (component.Broken) + args.Cancel(); + } + private void OnPowerChanged(EntityUid uid, SmartFridgeComponent component, ref PowerChangedEvent args) + { + TryUpdateVisualState(uid, component); + } + + private void OnBreak(EntityUid uid, SmartFridgeComponent component, BreakageEventArgs eventArgs) + { + component.Broken = true; + TryUpdateVisualState(uid, component); + } + public void Deny(EntityUid uid, SmartFridgeComponent? сomponent = null) + { + if (!Resolve(uid, ref сomponent)) + return; + + if (сomponent.Denying) + return; + + сomponent.Denying = true; + Audio.PlayPvs(сomponent.SoundDeny, uid, AudioParams.Default.WithVolume(-2f)); + TryUpdateVisualState(uid, сomponent); + } + + /// + /// Tries to update the visuals of the component based on its current state. + /// + public void TryUpdateVisualState(EntityUid uid, SmartFridgeComponent? vendComponent = null) + { + if (!Resolve(uid, ref vendComponent)) + return; + + var finalState = SmartFridgeVisualState.Normal; + if (vendComponent.Broken) + { + finalState = SmartFridgeVisualState.Broken; + } + else if (vendComponent.Denying) + { + finalState = SmartFridgeVisualState.Deny; + } + else if (!this.IsPowered(uid, EntityManager)) + { + finalState = SmartFridgeVisualState.Off; + } + + _appearanceSystem.SetData(uid, SmartFridgeVisuals.VisualState, finalState); + } + } +} diff --git a/Content.Shared/SS220/SmartFridge/SharedSmartFridgeSystem.cs b/Content.Shared/SS220/SmartFridge/SharedSmartFridgeSystem.cs new file mode 100644 index 000000000000..0082e2849e27 --- /dev/null +++ b/Content.Shared/SS220/SmartFridge/SharedSmartFridgeSystem.cs @@ -0,0 +1,98 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Content.Shared.Storage; +using Content.Shared.VendingMachines; +using Content.Shared.Hands.Components; +using Content.Shared.ActionBlocker; +using Robust.Shared.Audio.Systems; +using Content.Shared.Hands.EntitySystems; + +namespace Content.Shared.SS220.SmartFridge; +public abstract class SharedSmartFridgeSystem : EntitySystem +{ + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] protected readonly SharedAudioSystem Audio = default!; + [Dependency] private readonly SharedHandsSystem _sharedHandsSystem = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteractWithItem); + } + + //transfering storage into List + public List GetAllInventory(EntityUid uid, StorageComponent? component = null) + { + if (!Resolve(uid, ref component)) + return new(); + + Dictionary sortedInventory = new(); + + foreach (var item in component.Container.ContainedEntities) + { + if (!TryAddItem(item, sortedInventory)) + continue; + } + + var inventory = new List(sortedInventory.Values); + + return inventory; + } + private bool TryAddItem(EntityUid entityUid, Dictionary sortedInventory) + { + if (!_entity.TryGetComponent(entityUid, out var metadata)) + return false; + + if (sortedInventory.ContainsKey(metadata.EntityName) && + sortedInventory.TryGetValue(metadata.EntityName, out var entry)) + { + entry.Amount++; + entry.EntityUids.Add(GetNetEntity(entityUid)); + return true; + } + + + sortedInventory.Add(metadata.EntityName, + new VendingMachineInventoryEntry(InventoryType.Regular, metadata.EntityName, 1, GetNetEntity(entityUid)) + ); + + return true; + } + + private void OnInteractWithItem(EntityUid uid, StorageComponent storageComp, SmartFridgeInteractWithItemEvent args) + { + if (args.Session.AttachedEntity is not { } player) + return; + + var entity = GetEntity(args.InteractedItemUID); + + if (!Exists(entity)) + { + Log.Error($"Player {args.Session} interacted with non-existent item {args.InteractedItemUID} stored in {ToPrettyString(uid)}"); + return; + } + + if (!_actionBlockerSystem.CanInteract(player, entity) || !storageComp.Container.Contains(entity)) + return; + + // Does the player have hands? + if (!TryComp(player, out HandsComponent? hands) || hands.Count == 0) + return; + + // If the user's active hand is empty, try pick up the item. + if (hands.ActiveHandEntity == null) + { + if (_sharedHandsSystem.TryPickupAnyHand(player, entity, handsComp: hands) + && storageComp.StorageRemoveSound != null) + Audio.PlayPredicted(storageComp.StorageRemoveSound, uid, player); + { + return; + } + } + else + { + storageComp.Container.Remove(entity); + } + } +} diff --git a/Content.Shared/SS220/SmartFridge/SmartFridgeComponent.cs b/Content.Shared/SS220/SmartFridge/SmartFridgeComponent.cs new file mode 100644 index 000000000000..97c20f6ca001 --- /dev/null +++ b/Content.Shared/SS220/SmartFridge/SmartFridgeComponent.cs @@ -0,0 +1,123 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Audio; + +namespace Content.Shared.SS220.SmartFridge +{ + [RegisterComponent, NetworkedComponent] + public sealed partial class SmartFridgeComponent : Component + { + /// + /// 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. + /// + [DataField("denyDelay")] + public float DenyDelay = 2.0f; + + public bool Denying; + public bool Broken; + + /// + /// Sound that plays when an item can't be ejected + /// + [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"); + + #region Client Visuals + /// + /// RSI state for when the vending machine is unpowered. + /// Will be displayed on the layer + /// + [DataField("offState")] + public string? OffState; + + /// + /// RSI state for the screen of the vending machine + /// Will be displayed on the layer + /// + [DataField("screenState")] + public string? ScreenState; + + /// + /// RSI state for the vending machine's normal state. Usually a looping animation. + /// Will be displayed on the layer + /// + [DataField("normalState")] + public string? NormalState; + + /// + /// RSI state for the vending machine's deny animation. Will either be played once as sprite flick + /// or looped depending on how is set. + /// Will be displayed on the layer + /// + [DataField("denyState")] + public string? DenyState; + + /// + /// RSI state for when the vending machine is unpowered. + /// Will be displayed on the layer + /// + [DataField("brokenState")] + public string? BrokenState; + + /// + /// If set to true (default) will loop the animation of the for the duration + /// of . If set to false will play a sprite + /// flick animation for the state and then linger on the final frame until the end of the delay. + /// + [DataField("loopDeny")] + public bool LoopDenyAnimation = true; + #endregion + } + + [Serializable, NetSerializable] + public enum SmartFridgeVisuals + { + VisualState + } + + [Serializable, NetSerializable] + public enum SmartFridgeVisualState + { + Normal, + Off, + Broken, + Deny, + } + + [Serializable, NetSerializable] + public enum SmartFridgeVisualLayers : byte + { + /// + /// Off / Broken. The other layers will overlay this if the machine is on. + /// + Base, + /// + /// Normal / Deny / Eject + /// + BaseUnshaded, + /// + /// Screens that are persistent (where the machine is not off or broken) + /// + Screen + } + + [Serializable, NetSerializable] + public enum SmartFridgeUiKey + { + Key, + } + + [Serializable, NetSerializable] + public sealed class SmartFridgeInteractWithItemEvent : BoundUserInterfaceMessage + { + public readonly NetEntity InteractedItemUID; + public SmartFridgeInteractWithItemEvent(NetEntity interactedItemUID) + { + InteractedItemUID = interactedItemUID; + } + } +} diff --git a/Resources/Locale/ru-RU/ss220/smartftidge.ftl b/Resources/Locale/ru-RU/ss220/smartftidge.ftl new file mode 100644 index 000000000000..7be4457b98e6 --- /dev/null +++ b/Resources/Locale/ru-RU/ss220/smartftidge.ftl @@ -0,0 +1,2 @@ +ent-StorageSmartFridge = Умный холодильник + .desc = Охлаждаемое хранилище для складирования лекарств и химикатов. diff --git a/Resources/Prototypes/SS220/Entities/Structures/Machines/powered_storage.yml b/Resources/Prototypes/SS220/Entities/Structures/Machines/powered_storage.yml new file mode 100644 index 000000000000..56f7bcb66c0c --- /dev/null +++ b/Resources/Prototypes/SS220/Entities/Structures/Machines/powered_storage.yml @@ -0,0 +1,116 @@ +- type: entity + id: MachineStorage + parent: BaseMachinePowered + name: MachineStorage + description: its just a backpack, but too heavy, and with additional features + abstract: true + components: + - type: AmbientOnPowered + - type: AmbientSound + volume: -9 + range: 3 + enabled: false + sound: + path: /Audio/Ambience/Objects/vending_machine_hum.ogg + - type: Sprite + sprite: Structures/Machines/VendingMachines/empty.rsi + snapCardinals: true + - type: Physics + bodyType: Static + - type: Transform + noRot: true + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.45,0.25,0.45" + mask: + - MachineMask + layer: + - MachineLayer + density: 200 + - type: ActivatableUI + key: enum.SmartFridgeUiKey.Key + - type: ActivatableUIRequiresPower + - type: UserInterface + interfaces: + - key: enum.SmartFridgeUiKey.Key + type: SmartFridgeBoundUserInterface + - type: Anchorable + - type: DoAfter + - type: Electrified + enabled: false + usesApcPower: true + - type: PointLight + enabled: false + castShadows: false + radius: 1.5 + - type: LitOnPowered + - type: ApcPowerReceiver + powerLoad: 20 + priority: Low + - type: Actions + - type: Appearance + - type: ContainerContainer + containers: + storagebase: !type:Container + ents: [] + + +- type: entity + parent: MachineStorage + id: StorageSmartFridge + name: SmartFridge + description: A refrigerated storage unit for keeping items cold and fresh. + components: + - type: SmartFridge + offState: off + brokenState: broken + normalState: normal-unshaded + denyState: deny-unshaded + loopDeny: false + - type: Sprite + sprite: Structures/Machines/VendingMachines/smartfridge.rsi + layers: + - state: off + map: ["enum.SmartFridgeVisualLayers.Base"] + - state: off + map: ["enum.SmartFridgeVisualLayers.BaseUnshaded"] + shader: unshaded + - state: fill-3 + map: ["enum.PowerDeviceVisuals.Powered"] + - type: GenericVisualizer + visuals: + enum.PowerDeviceVisuals.Powered: + base: + True: { state: fill-3 } + False: { state: off } + - type: PointLight + radius: 1.5 + energy: 1.6 + color: "#9dc5c9" + - type: Storage + grid: + - 0,0,20,20 + maxItemSize: Huge + whitelist: + components: + - Pill + - Seed + - Produce + tags: + - Bottle + - Ointment + - Bloodpack + - Brutepack + - GlassBeaker + - PillCanister + - Meat + - Fruit + - Corn + - ClothMade + - Ambrosia + - Flower + - Carrot + - Potato diff --git a/Resources/migration.yml b/Resources/migration.yml index 75372b6acb1c..397deda42cb0 100644 --- a/Resources/migration.yml +++ b/Resources/migration.yml @@ -95,5 +95,9 @@ GeneratorUraniumMachineCircuitboard: PortableGeneratorSuperPacmanMachineCircuitb # SS220 2023-10-09 SignPsych: SignPsychology + # SS220 2023-12-06 SignNtFlag: NTFlag + +# SS220 2023-12-10 +VendingMachineSmartFridge: StorageSmartFridge