diff --git a/Content.IntegrationTests/Tests/Kitchen/KitchenTest.cs b/Content.IntegrationTests/Tests/Kitchen/KitchenTest.cs deleted file mode 100644 index c8d6409aab..0000000000 --- a/Content.IntegrationTests/Tests/Kitchen/KitchenTest.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Threading.Tasks; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.Kitchen; -using NUnit.Framework; -using Robust.Shared.Prototypes; - -namespace Content.IntegrationTests.Tests.Kitchen -{ - [TestFixture] - public class KitchenTest : ContentIntegrationTest - { - [Test] - public async Task TestRecipesValid() - { - var server = StartServer(); - await server.WaitIdleAsync(); - - var protoManager = server.ResolveDependency(); - - await server.WaitAssertion(() => - { - foreach (var recipe in protoManager.EnumeratePrototypes()) - { - Assert.That(protoManager.HasIndex(recipe.Result), $"Cannot find FoodRecipe result {recipe.Result} in {recipe.ID}"); - - foreach (var (solid, amount) in recipe.IngredientsSolids) - { - Assert.That(protoManager.HasIndex(solid), $"Cannot find FoodRecipe solid {solid} in {recipe.ID}"); - Assert.That(amount > 0, $" FoodRecipe {recipe.ID} has invalid solid amount of {amount}"); - } - - foreach (var (reagent, amount) in recipe.IngredientsReagents) - { - Assert.That(protoManager.HasIndex(reagent), $"Cannot find FoodRecipe reagent {reagent} in {recipe.ID}"); - Assert.That(amount > 0, $" FoodRecipe {recipe.ID} has invalid reagent amount of {amount}"); - } - - Assert.That(recipe.CookTime > 0, $"Cook time of {recipe.CookTime} for FoodRecipe {recipe.ID} is invalid!"); - } - }); - } - } -} diff --git a/Content.Server/Chemistry/ReactionEffects/CreateEntityReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/CreateEntityReactionEffect.cs new file mode 100644 index 0000000000..774df45c64 --- /dev/null +++ b/Content.Server/Chemistry/ReactionEffects/CreateEntityReactionEffect.cs @@ -0,0 +1,42 @@ +using Content.Shared.Chemistry.Reagent; +using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Chemistry.ReactionEffects; + +[DataDefinition] +public class CreateEntityReactionEffect : ReagentEffect +{ + /// + /// What entity to create. + /// + [DataField("entity", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] + public string Entity = default!; + + /// + /// How many entities to create per unit reaction. + /// + [DataField("number")] + public uint Number = 1; + + public override void Effect(ReagentEffectArgs args) + { + var transform = args.EntityManager.GetComponent(args.SolutionEntity); + var quantity = Number * args.Quantity.Int(); + + for (var i = 0; i < quantity; i++) + { + args.EntityManager.SpawnEntity(Entity, transform.MapPosition); + + // TODO figure out how to spawn inside of containers + // e.g. cheese: + // if the user is holding a bowl milk & enzyme, should drop to floor, not attached to the user. + // if reaction happens in a backpack, should insert cheese into backpack. + // --> if it doesn't fit, iterate through parent storage until it attaches to the grid (again, DON'T attach to players). + // if the reaction happens INSIDE a stomach? the bloodstream? I have no idea how to handle that. + // presumably having cheese materialize inside of your blood would have "disadvantages". + } + } +} diff --git a/Content.Server/Chemistry/TileReactions/CreateEntityReaction.cs b/Content.Server/Chemistry/TileReactions/CreateEntityTileReaction.cs similarity index 97% rename from Content.Server/Chemistry/TileReactions/CreateEntityReaction.cs rename to Content.Server/Chemistry/TileReactions/CreateEntityTileReaction.cs index 35cbfafc9f..b6817de8c5 100644 --- a/Content.Server/Chemistry/TileReactions/CreateEntityReaction.cs +++ b/Content.Server/Chemistry/TileReactions/CreateEntityTileReaction.cs @@ -16,7 +16,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy namespace Content.Server.Chemistry.TileReactions; [DataDefinition] -public class CreateEntityReaction : ITileReaction +public class CreateEntityTileReaction : ITileReaction { [DataField("entity", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))] public string Entity = default!; diff --git a/Content.Shared/Chemistry/Reaction/ReactionPrototype.cs b/Content.Shared/Chemistry/Reaction/ReactionPrototype.cs index 6101aa351a..f117884b0c 100644 --- a/Content.Shared/Chemistry/Reaction/ReactionPrototype.cs +++ b/Content.Shared/Chemistry/Reaction/ReactionPrototype.cs @@ -45,6 +45,12 @@ namespace Content.Shared.Chemistry.Reaction // TODO SERV3: Empty on the client, (de)serialize on the server with module manager is server module [DataField("sound", serverOnly: true)] public SoundSpecifier Sound { get; private set; } = new SoundPathSpecifier("/Audio/Effects/Chemistry/bubbles.ogg"); + + /// + /// If true, this reaction will only consume only integer multiples of the reactant amounts. If there are not + /// enough reactants, the reaction does not occur. Useful for spawn-entity reactions (e.g. creating cheese). + /// + [DataField("quantized")] public bool Quantized = false; } /// diff --git a/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs b/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs index ba43dd9983..d2a58d30d8 100644 --- a/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs +++ b/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs @@ -46,6 +46,17 @@ namespace Content.Shared.Chemistry.Reaction if (!solution.ContainsReagent(reactantName, out var reactantQuantity)) return false; + if (reactantData.Value.Catalyst) + { + // catalyst is not consumed, so will not limit the reaction. But it still needs to be present, and + // for quantized reactions we need to have a minimum amount + + if (reactantQuantity == FixedPoint2.Zero || reaction.Quantized && reactantQuantity < reactantCoefficient) + return false; + + continue; + } + var unitReactions = reactantQuantity / reactantCoefficient; if (unitReactions < lowestUnitReactions) @@ -53,7 +64,11 @@ namespace Content.Shared.Chemistry.Reaction lowestUnitReactions = unitReactions; } } - return true; + + if (reaction.Quantized) + lowestUnitReactions = (int) lowestUnitReactions; + + return lowestUnitReactions > 0; } /// diff --git a/Content.Shared/Kitchen/MicrowaveMealRecipePrototype.cs b/Content.Shared/Kitchen/MicrowaveMealRecipePrototype.cs index 891cba433e..051e6c698b 100644 --- a/Content.Shared/Kitchen/MicrowaveMealRecipePrototype.cs +++ b/Content.Shared/Kitchen/MicrowaveMealRecipePrototype.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; +using Content.Shared.Chemistry.Reagent; using Robust.Shared.Localization; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; using Robust.Shared.ViewVariables; namespace Content.Shared.Kitchen @@ -19,21 +22,21 @@ namespace Content.Shared.Kitchen [DataField("name")] private string _name = string.Empty; - [DataField("reagents")] - private readonly Dictionary _ingsReagents = new(); + [DataField("reagents", customTypeSerializer:typeof(PrototypeIdDictionarySerializer))] + private readonly Dictionary _ingsReagents = new(); - [DataField("solids")] - private readonly Dictionary _ingsSolids = new (); + [DataField("solids", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] + private readonly Dictionary _ingsSolids = new (); - [DataField("result")] + [DataField("result", customTypeSerializer: typeof(PrototypeIdSerializer))] public string Result { get; } = string.Empty; [DataField("time")] - public int CookTime { get; } = 5; + public uint CookTime { get; } = 5; public string Name => Loc.GetString(_name); - public IReadOnlyDictionary IngredientsReagents => _ingsReagents; - public IReadOnlyDictionary IngredientsSolids => _ingsSolids; + public IReadOnlyDictionary IngredientsReagents => _ingsReagents; + public IReadOnlyDictionary IngredientsSolids => _ingsSolids; } } diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml index 4e2a86a3ab..4e7d0fb571 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml @@ -34,6 +34,14 @@ - id: MonkeyCubeBox - id: SprayBottleWater - id: ReagentContainerFlour + amount: 2 + - id: ReagentContainerSugar + - id: FoodCondimentBottleEnzyme + # really, milk should go in the fridge. Unfortunately saltern only has freezers. + # yes, I'm using this as an excuse to not have to do extra work. + - id: ReagentContainerMilk + amount: 2 + - id: ReagentContainerMilkSoy - type: entity id: ClosetJanitorFilled diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/dinnerware.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/dinnerware.yml index 865e98cabc..56de4f5c48 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/dinnerware.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/dinnerware.yml @@ -5,6 +5,7 @@ startingInventory: ButchCleaver: 1 KitchenKnife: 5 + FoodBowlBig: 5 DrinkGlass: 10 DrinkMug: 5 DrinkMugBlack: 2 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml index 35995cf73a..f454ad48a9 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml @@ -2,14 +2,14 @@ - type: entity name: bowl - parent: BaseItem + parent: ReagentContainerBase id: FoodBowlBig description: A simple bowl, used for soups and salads. components: - type: SolutionContainerManager solutions: food: - maxVol: 20 + maxVol: 50 # enough to make cheese in. - type: Sprite sprite: Objects/Consumable/Food/bowl.rsi state: bowl @@ -22,17 +22,11 @@ damage: types: Blunt: 5 - - type: Spillable - solution: food - # I suspect this ought to be more of a transformable container kinda thing. - # For now, these components make it usable. - type: Drink solution: food isOpen: true - type: DrawableSolution solution: food - - type: RefillableSolution - solution: food - type: Damageable damageContainer: Inorganic - type: Destructible diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/condiments.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/condiments.yml index 663e035973..75f5401d29 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/condiments.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/condiments.yml @@ -337,6 +337,7 @@ description: A thin glass bottle used to store condiments. components: - type: Drink + solution: food openSounds: collection: pop - type: SolutionContainerManager @@ -344,6 +345,8 @@ food: maxVol: 30 - type: SolutionTransfer + canChangeTransferAmount: true + minTransferAmount: 5 maxTransferAmount: 30 - type: Sprite netsync: false @@ -480,6 +483,7 @@ description: A smaller glass bottle used to store condiments. components: - type: Drink + solution: food openSounds: collection: pop - type: SolutionContainerManager diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml index a207070c35..0d56bc2b42 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml @@ -214,10 +214,10 @@ - type: SolutionContainerManager solutions: food: - maxVol: 20 + maxVol: 50 # at least enough for one cheese reagents: - ReagentId: Milk - Quantity: 20 + Quantity: 50 - type: entity parent: ReagentContainerBase @@ -230,10 +230,10 @@ - type: SolutionContainerManager solutions: food: - maxVol: 20 + maxVol: 50 reagents: - ReagentId: MilkSoy - Quantity: 20 + Quantity: 50 - type: entity parent: ReagentContainerBase diff --git a/Resources/Prototypes/Reagents/fun.yml b/Resources/Prototypes/Reagents/fun.yml index 33fd1920d8..e7337caa3a 100644 --- a/Resources/Prototypes/Reagents/fun.yml +++ b/Resources/Prototypes/Reagents/fun.yml @@ -5,7 +5,7 @@ physicalDesc: fibrous color: "#800000" tileReactions: - - !type:CreateEntityReaction + - !type:CreateEntityTileReaction entity: Carpet maxOnTileWhitelist: tags: [ Carpet ] @@ -31,7 +31,7 @@ physicalDesc: buzzy color: "#FFD35D" tileReactions: - - !type:CreateEntityReaction + - !type:CreateEntityTileReaction entity: MobBee usage: 2 maxOnTile: 2 diff --git a/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml b/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml index f87d20a33f..a70b65409c 100644 --- a/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml +++ b/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml @@ -4,19 +4,18 @@ name: cheeseburger recipe result: FoodBurgerCheese time: 5 - reagents: - Flour: 5 solids: + FoodBreadPlainSlice: 2 # its not a burger bun, but its good enough FoodMeat: 1 + FoodCheeseSlice: 1 - type: microwaveMealRecipe id: RecipeClownBurger name: clownburger recipe result: FoodBurgerClown time: 5 - reagents: - Flour: 5 solids: + FoodBreadPlainSlice: 2 ClothingMaskClown: 1 # - type: microwaveMealRecipe @@ -34,9 +33,8 @@ name: xenoburger recipe result: FoodBurgerXeno time: 5 - reagents: - Flour: 5 solids: + FoodBreadPlainSlice: 2 FoodMeatXeno: 1 #- type: microwaveMealRecipe @@ -60,8 +58,8 @@ name: bread recipe result: FoodBreadPlain time: 15 - reagents: - Flour: 15 + solids: + FoodDough: 1 # - type: microwaveMealRecipe # id: RecipeSandwich @@ -105,9 +103,8 @@ name: cream cheese bread recipe result: FoodBreadCreamcheese time: 20 - reagents: - Flour: 15 solids: + FoodDough: 1 FoodCheeseSlice: 2 - type: microwaveMealRecipe @@ -115,11 +112,8 @@ name: banana bread recipe result: FoodBreadBanana time: 25 - reagents: - Flour: 15 - Milk: 5 solids: - FoodEgg: 3 + FoodDough: 1 FoodBanana: 1 #Pizzas @@ -128,9 +122,8 @@ name: margherita pizza recipe result: FoodPizzaMargherita time: 30 - reagents: - Flour: 10 solids: + FoodDough: 1 FoodCheeseSlice: 4 FoodTomato: 1 @@ -139,9 +132,8 @@ name: mushroom pizza recipe result: FoodPizzaMushroom time: 25 - reagents: - Flour: 10 solids: + FoodDough: 1 FoodMushroom: 5 - type: microwaveMealRecipe @@ -149,9 +141,8 @@ name: meat pizza recipe result: FoodPizzaMeat time: 30 - reagents: - Flour: 10 solids: + FoodDough: 1 FoodMeat: 3 FoodCheeseSlice: 1 FoodTomato: 1 @@ -161,9 +152,8 @@ name: vegetable pizza recipe result: FoodPizzaVegetable time: 30 - reagents: - Flour: 10 solids: + FoodDough: 1 FoodEggplant: 1 FoodCarrot: 1 FoodCorn: 1 @@ -365,9 +355,8 @@ name: banana cream pie result: FoodPieBananaCream time: 15 - reagents: - Flour: 10 solids: + FoodCakeBatter: 1 # should really be pie pastry or whatever. Good enough. FoodBanana: 3 #Donks i guess diff --git a/Resources/Prototypes/Recipes/Reactions/food.yml b/Resources/Prototypes/Recipes/Reactions/food.yml new file mode 100644 index 0000000000..0b7740a086 --- /dev/null +++ b/Resources/Prototypes/Recipes/Reactions/food.yml @@ -0,0 +1,59 @@ +- type: reaction + id: Curdling + impact: Low + quantized: true + reactants: + Milk: + amount: 40 + Enzyme: + amount: 5 + catalyst: true + effects: + - !type:CreateEntityReactionEffect + entity: FoodCheese + +- type: reaction + id: CreateDough + impact: Low + quantized: true + reactants: + Flour: + amount: 15 + Water: + amount: 10 + effects: + - !type:CreateEntityReactionEffect + entity: FoodDough + +- type: reaction + id: CreateCakeBatter + impact: Low + quantized: true + reactants: + Flour: + amount: 15 + Egg: + amount: 12 + Sugar: + amount: 5 + effects: + - !type:CreateEntityReactionEffect + entity: FoodCakeBatter + +# TG has a cake recipe that uses soy milk instead of eggs. +# but afaik it spawns the exact same cake batter entity. +# Maybe change this if you want to do allergies or something +- type: reaction + id: CreateVeganCakeBatter + impact: Low + quantized: true + reactants: + Flour: + amount: 15 + MilkSoy: + amount: 12 + Sugar: + amount: 5 + effects: + - !type:CreateEntityReactionEffect + entity: FoodCakeBatter