Remove FoodContainer, add whitelists, replace with SpawnItemsOnUse (#4358)

This commit is contained in:
mirrorcult
2021-07-25 08:41:50 -07:00
committed by GitHub
parent ae84ba07e6
commit a91f919018
18 changed files with 253 additions and 260 deletions

View File

@@ -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"
};
}
}

View File

@@ -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<ISpriteComponent>();
if (!component.TryGetData<int>(FoodContainerVisuals.Current, out var current))
{
return;
}
if (!component.TryGetData<int>(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}");
}
}
}

View File

@@ -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
{
/// <summary>
/// This container acts as a master object for things like Pizza, which holds slices.
/// </summary>
/// 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<string, int>? _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<ItemComponent>());
_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);
}
}
}

View File

@@ -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;
}

View File

@@ -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
{
/// <summary>
/// Spawns items when used in hand.
/// </summary>
[RegisterComponent]
public class SpawnItemsOnUseComponent : Component
{
public override string Name => "SpawnItemsOnUse";
/// <summary>
/// The list of entities to spawn, with amounts and orGroups.
/// </summary>
/// <returns></returns>
[DataField("items", required: true)]
public List<EntitySpawnEntry> Items = new List<EntitySpawnEntry>();
/// <summary>
/// A sound to play when the items are spawned. For example, gift boxes being unwrapped.
/// </summary>
[DataField("sound")]
public SoundSpecifier? Sound = null;
/// <summary>
/// How many uses before the item should delete itself.
/// </summary>
/// <returns></returns>
[DataField("uses")]
public int Uses = 1;
}
}

View File

@@ -17,9 +17,9 @@ namespace Content.Server.Storage.Components
{
public override string Name => "StorageFill";
[DataField("contents")] private List<StorageFillEntry> _contents = new();
[DataField("contents")] private List<EntitySpawnEntry> _contents = new();
public IReadOnlyList<StorageFillEntry> Contents => _contents;
public IReadOnlyList<EntitySpawnEntry> Contents => _contents;
void IMapInit.MapInit()
{
@@ -39,7 +39,6 @@ namespace Content.Server.Storage.Components
var alreadySpawnedGroups = new List<string>();
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<EntityPrototype>))]
public string? PrototypeId;
[DataField("prob")] public float SpawnProbability;
/// <summary>
/// The probability that an item will spawn. Takes decimal form so 0.05 is 5%, 0.50 is 50% etc.
/// </summary>
[DataField("orGroup")] public string GroupId;
/// <summary>
/// orGroup signifies to pick between entities designated with an ID.
///
/// <example>
/// <para>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.
///
/// </para>
/// <code>
/// - type: StorageFill
/// contents:
/// - name: X
/// - name: Y
/// prob: 0.50
/// orGroup: YOrZ
/// - name: Z
/// orGroup: YOrZ
/// </code>
/// </example>
/// </summary>
[DataField("amount")] public int Amount;
public void PopulateDefaultValues()
{
Amount = 1;
SpawnProbability = 1;
}
}
}
}

View File

@@ -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
{
/// <summary>
/// Dictates a list of items that can be spawned.
/// </summary>
[Serializable]
[DataDefinition]
public struct EntitySpawnEntry : IPopulateDefaultValues
{
[DataField("id", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string PrototypeId;
/// <summary>
/// The probability that an item will spawn. Takes decimal form so 0.05 is 5%, 0.50 is 50% etc.
/// </summary>
[DataField("prob")]
public float SpawnProbability;
/// <summary>
/// orGroup signifies to pick between entities designated with an ID.
///
/// <example>
/// <para>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.
///
/// </para>
/// <code>
/// - type: StorageFill
/// contents:
/// - name: X
/// - name: Y
/// prob: 0.50
/// orGroup: YOrZ
/// - name: Z
/// orGroup: YOrZ
/// </code>
/// </example>
/// </summary>
[DataField("orGroup")]
public string? GroupId;
[DataField("amount")]
public int Amount;
public void PopulateDefaultValues()
{
Amount = 1;
SpawnProbability = 1;
}
}
}

View File

@@ -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

View File

@@ -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<SpawnItemsOnUseComponent, UseInHandEvent>(OnUseInHand);
}
private void OnUseInHand(EntityUid uid, SpawnItemsOnUseComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
var owner = EntityManager.GetEntity(uid);
var alreadySpawnedGroups = new List<string>();
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<SharedHandsComponent>(out var hands))
{
hands.TryPutInAnyHand(entityToPlaceInHands);
}
}
}
}

View File

@@ -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<StorageCounterComponent>(out var newStorageComp))
{
newStorageComp.ContainerUpdateAppearance(message.Container);

View File

@@ -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
{
/// <summary>
/// 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
/// </summary>
Discrete,
Rounded,
}
[NetSerializable, Serializable]
public enum FoodContainerVisuals
{
Capacity,
Current,
}
}

View File

@@ -1,8 +1,5 @@
# Base
- type: Tag
id: Donut
- type: entity
parent: BaseItem
id: FoodDonutBase

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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