diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index c8c591b40c..805c620ea0 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -57,7 +57,6 @@ namespace Content.Client.Entry "CablePlacer", "Drink", "Food", - "FoodContainer", "MagicMirror", "FloorTile", "ShuttleController", @@ -277,6 +276,7 @@ namespace Content.Client.Entry "Advertise", "PowerNetworkBattery", "BatteryCharger", + "SpawnItemsOnUse" }; } } diff --git a/Content.Client/Nutrition/Visualizers/DrinkFoodContainerVisualizer.cs b/Content.Client/Nutrition/Visualizers/DrinkFoodContainerVisualizer.cs deleted file mode 100644 index 5117ebe5e2..0000000000 --- a/Content.Client/Nutrition/Visualizers/DrinkFoodContainerVisualizer.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using Content.Shared.Nutrition.Components; -using Content.Shared.Rounding; -using JetBrains.Annotations; -using Robust.Client.GameObjects; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Content.Client.Nutrition.Visualizers -{ - [UsedImplicitly] - public sealed class FoodContainerVisualizer : AppearanceVisualizer - { - [DataField("base_state", required: true)] - private string? _baseState; - - [DataField("steps", required: true)] - private int _steps; - - [DataField("mode")] - private FoodContainerVisualMode _mode = FoodContainerVisualMode.Rounded; - - public override void OnChangeData(AppearanceComponent component) - { - var sprite = component.Owner.GetComponent(); - - if (!component.TryGetData(FoodContainerVisuals.Current, out var current)) - { - return; - } - - if (!component.TryGetData(FoodContainerVisuals.Capacity, out var capacity)) - { - return; - } - - int step; - - switch (_mode) - { - case FoodContainerVisualMode.Discrete: - step = Math.Min(_steps - 1, current); - break; - case FoodContainerVisualMode.Rounded: - step = ContentHelpers.RoundToLevels(current, capacity, _steps); - break; - default: - throw new NullReferenceException(); - } - - sprite.LayerSetState(0, $"{_baseState}-{step}"); - } - } -} diff --git a/Content.Server/Nutrition/Components/FoodContainerComponent.cs b/Content.Server/Nutrition/Components/FoodContainerComponent.cs deleted file mode 100644 index 3854928bca..0000000000 --- a/Content.Server/Nutrition/Components/FoodContainerComponent.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Content.Server.Hands.Components; -using Content.Server.Items; -using Content.Shared.Interaction; -using Content.Shared.Nutrition.Components; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Random; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Utility; -using Robust.Shared.ViewVariables; - -namespace Content.Server.Nutrition.Components -{ - /// - /// This container acts as a master object for things like Pizza, which holds slices. - /// - /// TODO: Perhaps implement putting food back (pizza boxes) but that really isn't mandatory. - /// This doesn't even need to have an actual Container for right now. - [RegisterComponent] - public sealed class FoodContainer : SharedFoodContainerComponent, IUse - { - [Dependency] private readonly IRobustRandom _random = default!; - public override string Name => "FoodContainer"; - - private AppearanceComponent? _appearance; - [DataField("prototypes")] - private Dictionary? _prototypes = default; - [DataField("capacity")] - private uint _capacity = 5; - - public int Capacity => (int)_capacity; - [ViewVariables] - public int Count => _count; - - private int _count = 0; - - protected override void Initialize() - { - base.Initialize(); - Owner.TryGetComponent(out _appearance); - _count = Capacity; - UpdateAppearance(); - - } - - bool IUse.UseEntity(UseEntityEventArgs eventArgs) - { - if (!eventArgs.User.TryGetComponent(out HandsComponent? handsComponent)) - { - return false; - } - - var itemToSpawn = Owner.EntityManager.SpawnEntity(GetRandomPrototype(), Owner.Transform.Coordinates); - handsComponent.PutInHandOrDrop(itemToSpawn.GetComponent()); - _count--; - if (_count < 1) - { - Owner.Delete(); - return false; - } - - return true; - } - - private string? GetRandomPrototype() - { - var defaultProto = _prototypes?.Keys.FirstOrDefault(); - - if (defaultProto == null) - { - return null; - } - - DebugTools.AssertNotNull(_prototypes); - - if (_prototypes!.Count == 1) - { - return defaultProto; - } - - var probResult = _random.Next(0, 100); - var total = 0; - foreach (var item in _prototypes) - { - total += item.Value; - if (probResult < total) - { - return item.Key; - } - } - - return defaultProto; - } - - private void UpdateAppearance() - { - _appearance?.SetData(FoodContainerVisuals.Current, Count); - } - } -} diff --git a/Content.Server/Storage/Components/ServerStorageComponent.cs b/Content.Server/Storage/Components/ServerStorageComponent.cs index 34a51e5bbc..50b579437a 100644 --- a/Content.Server/Storage/Components/ServerStorageComponent.cs +++ b/Content.Server/Storage/Components/ServerStorageComponent.cs @@ -15,6 +15,7 @@ using Content.Shared.Item; using Content.Shared.Notification; using Content.Shared.Notification.Managers; using Content.Shared.Storage; +using Content.Shared.Whitelist; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Audio; @@ -47,10 +48,16 @@ namespace Content.Server.Storage.Components [DataField("occludesLight")] private bool _occludesLight = true; + [DataField("quickInsert")] - private bool _quickInsert; //Can insert storables by "attacking" them with the storage entity + private bool _quickInsert = false; // Can insert storables by "attacking" them with the storage entity + [DataField("areaInsert")] - private bool _areaInsert; //"Attacking" with the storage entity causes it to insert all nearby storables after a delay + private bool _areaInsert = false; // "Attacking" with the storage entity causes it to insert all nearby storables after a delay + + [DataField("whitelist")] + private EntityWhitelist? _whitelist = null; + private bool _storageInitialCalculated; private int _storageUsed; [DataField("capacity")] @@ -123,6 +130,11 @@ namespace Content.Server.Storage.Components return false; } + if (_whitelist != null && !_whitelist.IsValid(entity)) + { + return false; + } + return true; } diff --git a/Content.Server/Storage/Components/SpawnItemsOnUseComponent.cs b/Content.Server/Storage/Components/SpawnItemsOnUseComponent.cs new file mode 100644 index 0000000000..0956e8a5f3 --- /dev/null +++ b/Content.Server/Storage/Components/SpawnItemsOnUseComponent.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Content.Shared.Sound; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Storage.Components +{ + /// + /// Spawns items when used in hand. + /// + [RegisterComponent] + public class SpawnItemsOnUseComponent : Component + { + public override string Name => "SpawnItemsOnUse"; + + /// + /// The list of entities to spawn, with amounts and orGroups. + /// + /// + [DataField("items", required: true)] + public List Items = new List(); + + /// + /// A sound to play when the items are spawned. For example, gift boxes being unwrapped. + /// + [DataField("sound")] + public SoundSpecifier? Sound = null; + + /// + /// How many uses before the item should delete itself. + /// + /// + [DataField("uses")] + public int Uses = 1; + } +} diff --git a/Content.Server/Storage/Components/StorageFillComponent.cs b/Content.Server/Storage/Components/StorageFillComponent.cs index 788a1727bb..ed56832fb8 100644 --- a/Content.Server/Storage/Components/StorageFillComponent.cs +++ b/Content.Server/Storage/Components/StorageFillComponent.cs @@ -17,9 +17,9 @@ namespace Content.Server.Storage.Components { public override string Name => "StorageFill"; - [DataField("contents")] private List _contents = new(); + [DataField("contents")] private List _contents = new(); - public IReadOnlyList Contents => _contents; + public IReadOnlyList Contents => _contents; void IMapInit.MapInit() { @@ -39,7 +39,6 @@ namespace Content.Server.Storage.Components var alreadySpawnedGroups = new List(); foreach (var storageItem in _contents) { - if (string.IsNullOrEmpty(storageItem.PrototypeId)) continue; if (!string.IsNullOrEmpty(storageItem.GroupId) && alreadySpawnedGroups.Contains(storageItem.GroupId)) continue; @@ -58,50 +57,5 @@ namespace Content.Server.Storage.Components if (!string.IsNullOrEmpty(storageItem.GroupId)) alreadySpawnedGroups.Add(storageItem.GroupId); } } - - [Serializable] - [DataDefinition] - public struct StorageFillEntry : IPopulateDefaultValues - { - [DataField("id", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? PrototypeId; - - [DataField("prob")] public float SpawnProbability; - - /// - /// The probability that an item will spawn. Takes decimal form so 0.05 is 5%, 0.50 is 50% etc. - /// - [DataField("orGroup")] public string GroupId; - - /// - /// orGroup signifies to pick between entities designated with an ID. - /// - /// - /// To define an orGroup in a StorageFill component you - /// need to add it to the entities you want to choose between and - /// add a prob field. In this example there is a 50% chance the storage - /// spawns with Y or Z. - /// - /// - /// - /// - type: StorageFill - /// contents: - /// - name: X - /// - name: Y - /// prob: 0.50 - /// orGroup: YOrZ - /// - name: Z - /// orGroup: YOrZ - /// - /// - /// - [DataField("amount")] public int Amount; - - public void PopulateDefaultValues() - { - Amount = 1; - SpawnProbability = 1; - } - } } } diff --git a/Content.Server/Storage/EntitySpawnEntry.cs b/Content.Server/Storage/EntitySpawnEntry.cs new file mode 100644 index 0000000000..99080980c0 --- /dev/null +++ b/Content.Server/Storage/EntitySpawnEntry.cs @@ -0,0 +1,59 @@ +using System; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Storage +{ + /// + /// Dictates a list of items that can be spawned. + /// + [Serializable] + [DataDefinition] + public struct EntitySpawnEntry : IPopulateDefaultValues + { + [DataField("id", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] + public string PrototypeId; + + /// + /// The probability that an item will spawn. Takes decimal form so 0.05 is 5%, 0.50 is 50% etc. + /// + [DataField("prob")] + public float SpawnProbability; + + /// + /// orGroup signifies to pick between entities designated with an ID. + /// + /// + /// To define an orGroup in a StorageFill component you + /// need to add it to the entities you want to choose between and + /// add a prob field. In this example there is a 50% chance the storage + /// spawns with Y or Z. + /// + /// + /// + /// - type: StorageFill + /// contents: + /// - name: X + /// - name: Y + /// prob: 0.50 + /// orGroup: YOrZ + /// - name: Z + /// orGroup: YOrZ + /// + /// + /// + [DataField("orGroup")] + public string? GroupId; + + [DataField("amount")] + public int Amount; + + public void PopulateDefaultValues() + { + Amount = 1; + SpawnProbability = 1; + } + } +} diff --git a/Content.Server/Storage/ItemCounterSystem.cs b/Content.Server/Storage/EntitySystems/ItemCounterSystem.cs similarity index 96% rename from Content.Server/Storage/ItemCounterSystem.cs rename to Content.Server/Storage/EntitySystems/ItemCounterSystem.cs index 9e79ab984f..cdd53e335c 100644 --- a/Content.Server/Storage/ItemCounterSystem.cs +++ b/Content.Server/Storage/EntitySystems/ItemCounterSystem.cs @@ -5,7 +5,7 @@ using JetBrains.Annotations; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -namespace Content.Server.Storage +namespace Content.Server.Storage.EntitySystems { [UsedImplicitly] public class ItemCounterSystem : SharedItemCounterSystem diff --git a/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs b/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs new file mode 100644 index 0000000000..e694aa237a --- /dev/null +++ b/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using Content.Server.Storage.Components; +using Content.Shared.Hands.Components; +using Content.Shared.Interaction; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Player; +using Robust.Shared.Random; + +namespace Content.Server.Storage.EntitySystems +{ + public class SpawnItemsOnUseSystem : EntitySystem + { + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUseInHand); + } + + private void OnUseInHand(EntityUid uid, SpawnItemsOnUseComponent component, UseInHandEvent args) + { + if (args.Handled) + return; + + var owner = EntityManager.GetEntity(uid); + var alreadySpawnedGroups = new List(); + IEntity? entityToPlaceInHands = null; + foreach (var storageItem in component.Items) + { + if (!string.IsNullOrEmpty(storageItem.GroupId) && + alreadySpawnedGroups.Contains(storageItem.GroupId)) continue; + + if (storageItem.SpawnProbability != 1f && + !_random.Prob(storageItem.SpawnProbability)) + { + continue; + } + + for (var i = 0; i < storageItem.Amount; i++) + { + entityToPlaceInHands = EntityManager.SpawnEntity(storageItem.PrototypeId, args.User.Transform.Coordinates); + } + + if (!string.IsNullOrEmpty(storageItem.GroupId)) alreadySpawnedGroups.Add(storageItem.GroupId); + } + + if (component.Sound != null) + SoundSystem.Play(Filter.Pvs(owner), component.Sound.GetSound()); + + component.Uses--; + if (component.Uses == 0) + { + args.Handled = true; + owner.Delete(); + } + + if (entityToPlaceInHands != null + && args.User.TryGetComponent(out var hands)) + { + hands.TryPutInAnyHand(entityToPlaceInHands); + } + } + } +} diff --git a/Content.Server/Storage/StorageSystem.cs b/Content.Server/Storage/EntitySystems/StorageSystem.cs similarity index 98% rename from Content.Server/Storage/StorageSystem.cs rename to Content.Server/Storage/EntitySystems/StorageSystem.cs index 73cf367254..d80a8e32a9 100644 --- a/Content.Server/Storage/StorageSystem.cs +++ b/Content.Server/Storage/EntitySystems/StorageSystem.cs @@ -6,7 +6,7 @@ using Robust.Server.Player; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -namespace Content.Server.Storage +namespace Content.Server.Storage.EntitySystems { [UsedImplicitly] internal sealed class StorageSystem : EntitySystem @@ -54,7 +54,7 @@ namespace Content.Server.Storage { storageComp.HandleEntityMaybeInserted(message); } - + if (oldParentEntity.TryGetComponent(out var newStorageComp)) { newStorageComp.ContainerUpdateAppearance(message.Container); diff --git a/Content.Shared/Nutrition/Components/SharedDrinkFoodContainerComponent.cs b/Content.Shared/Nutrition/Components/SharedDrinkFoodContainerComponent.cs deleted file mode 100644 index a4fb1d0288..0000000000 --- a/Content.Shared/Nutrition/Components/SharedDrinkFoodContainerComponent.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization; - -namespace Content.Shared.Nutrition.Components -{ - public abstract class SharedFoodContainerComponent : Component - { - } - - [NetSerializable, Serializable] - public enum FoodContainerVisualMode - { - /// - /// Discrete: 50 eggs in a carton -> down to 25, will show 12/12 until it gets below max - /// Rounded: 50 eggs in a carton -> down to 25, will round it to 6 of 12 - /// - Discrete, - Rounded, - } - - [NetSerializable, Serializable] - public enum FoodContainerVisuals - { - Capacity, - Current, - } -} diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/donut.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/donut.yml index 32623c81ea..25b22164db 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/donut.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/donut.yml @@ -1,8 +1,5 @@ # Base -- type: Tag - id: Donut - - type: entity parent: BaseItem id: FoodDonutBase diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml index 87bfb0cd8c..c5d94a8ded 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml @@ -21,6 +21,9 @@ count: 8 - type: Item size: 8 + - type: Tag + tags: + - Pizza - type: entity parent: FoodPizzaBase @@ -37,6 +40,9 @@ Quantity: 5 - type: Item size: 1 + - type: Tag + tags: + - Pizza # Pizza diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml index b3264ef81e..da91d4b718 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml @@ -8,7 +8,7 @@ - type: entity parent: BaseItem id: FoodBoxDonut - name: donutbox + name: donut box description: Mmm, Donuts. components: - type: Sprite @@ -17,6 +17,9 @@ state: box - type: Storage capacity: 6 + whitelist: + tags: + - Donut - type: Item sprite: Objects/Consumable/Food/Baked/donut.rsi size: 6 @@ -48,7 +51,7 @@ - type: entity parent: BaseItem id: FoodContainerEgg - name: eggbox + name: egg carton description: Don't drop 'em! components: - type: Sprite @@ -57,6 +60,9 @@ state: box-closed - type: Storage capacity: 12 + whitelist: + tags: + - Egg - type: Item sprite: Objects/Consumable/Food/egg.rsi size: 12 @@ -114,6 +120,7 @@ - type: entity parent: FoodContainerEgg id: EggBoxBroken + suffix: Broken components: - type: StorageFill contents: @@ -261,6 +268,9 @@ sprite: Objects/Consumable/Food/Baked/donkpocket.rsi state: box - type: Storage + whitelist: + tags: + - DonkPocket capacity: 6 - type: Item sprite: Objects/Consumable/Food/Baked/donkpocket.rsi diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml index f2896a2ba0..886827dcd8 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml @@ -1,8 +1,5 @@ # Base -- type: Tag - id: Egg - - type: entity parent: BaseItem id: FoodEggBase @@ -66,7 +63,7 @@ - type: Puddle variants: 4 state: egg - + - type: entity name: eggshells parent: BaseItem @@ -86,6 +83,9 @@ reagents: - ReagentId: Egg Quantity: 1 + - type: Tag + tags: + - Egg # Egg diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/rolling_paper.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/rolling_paper.yml index 9a55077392..7308ac01ce 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/rolling_paper.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/rolling_paper.yml @@ -4,10 +4,15 @@ id: PackPaperRolling description: "A pack of thin pieces of paper used to make fine smokeables." components: - # I know but it just works. - - type: FoodContainer - prototypes: - PaperRolling: 100 + - type: Storage + whitelist: + tags: + - RollingPaper + capacity: 16 + - type: StorageFill + contents: + - id: PaperRolling + amount: 8 - type: Sprite sprite: Objects/Consumable/Smokeables/Cigarettes/paper.rsi state: cigpapers @@ -29,6 +34,10 @@ state: cigpaper - type: Item sprite: Objects/Consumable/Smokeables/Cigarettes/paper.rsi + size: 2 + - type: Tag + tags: + - RollingPaper - type: entity id: CigaretteFilter diff --git a/Resources/Prototypes/Entities/Objects/Misc/monkeycube.yml b/Resources/Prototypes/Entities/Objects/Misc/monkeycube.yml index 396e7ae7fe..263d026607 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/monkeycube.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/monkeycube.yml @@ -4,9 +4,15 @@ id: MonkeyCubeBox description: Drymate brand monkey cubes. Just add water! components: - - type: FoodContainer - prototypes: - MonkeyCubeWrapper: 100 + - type: Storage + whitelist: + tags: + - MonkeyCube + capacity: 30 + - type: StorageFill + contents: + - id: MonkeyCubeWrapped + amount: 6 - type: Sprite sprite: Objects/Misc/monkeycube.rsi state: box @@ -14,13 +20,18 @@ - type: entity parent: BaseItem name: monkey cube - id: MonkeyCubeWrapper + suffix: Wrapped + id: MonkeyCubeWrapped description: Unwrap this to get a monkey cube. components: - - type: FoodContainer - capacity: 1 - prototypes: - MonkeyCube: 100 + - type: SpawnItemsOnUse + items: + - id: MonkeyCube + sound: + path: /Audio/Effects/unwrap.ogg - type: Sprite sprite: Objects/Misc/monkeycube.rsi state: wrapper + - type: Tag + tags: + - MonkeyCube diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 07adc2a9fc..3b4028d5e7 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -58,6 +58,12 @@ - type: Tag id: DoorElectronics +- type: Tag + id: Donut + +- type: Tag + id: Egg + - type: Tag id: ExplosivePassable @@ -84,13 +90,19 @@ - type: Tag id: JawsOfLife - + +- type: Tag + id: MonkeyCube + - type: Tag id: NoSpinOnThrow - type: Tag id: Ore +- type: Tag + id: Pizza + - type: Tag id: PlantAnalyzer @@ -103,6 +115,9 @@ - type: Tag id: Powerdrill +- type: Tag + id: RollingPaper + - type: Tag id: Screwdriver