Add cheese & dough reactions (#5547)

* git mv

* rename CreateEntityReaction -> CreateEntityTileReaction

* cheese

* change microwave recipes

* remove unnecessary test

* TryGet->Get
This commit is contained in:
Leon Friedrich
2021-11-27 11:50:14 +13:00
committed by GitHub
parent 7ecd08b8ca
commit 61423be24e
14 changed files with 169 additions and 91 deletions

View File

@@ -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<IPrototypeManager>();
await server.WaitAssertion(() =>
{
foreach (var recipe in protoManager.EnumeratePrototypes<FoodRecipePrototype>())
{
Assert.That(protoManager.HasIndex<EntityPrototype>(recipe.Result), $"Cannot find FoodRecipe result {recipe.Result} in {recipe.ID}");
foreach (var (solid, amount) in recipe.IngredientsSolids)
{
Assert.That(protoManager.HasIndex<EntityPrototype>(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<ReagentPrototype>(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!");
}
});
}
}
}

View File

@@ -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
{
/// <summary>
/// What entity to create.
/// </summary>
[DataField("entity", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Entity = default!;
/// <summary>
/// How many entities to create per unit reaction.
/// </summary>
[DataField("number")]
public uint Number = 1;
public override void Effect(ReagentEffectArgs args)
{
var transform = args.EntityManager.GetComponent<TransformComponent>(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".
}
}
}

View File

@@ -16,7 +16,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Server.Chemistry.TileReactions; namespace Content.Server.Chemistry.TileReactions;
[DataDefinition] [DataDefinition]
public class CreateEntityReaction : ITileReaction public class CreateEntityTileReaction : ITileReaction
{ {
[DataField("entity", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))] [DataField("entity", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Entity = default!; public string Entity = default!;

View File

@@ -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 // 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"); [DataField("sound", serverOnly: true)] public SoundSpecifier Sound { get; private set; } = new SoundPathSpecifier("/Audio/Effects/Chemistry/bubbles.ogg");
/// <summary>
/// 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).
/// </summary>
[DataField("quantized")] public bool Quantized = false;
} }
/// <summary> /// <summary>

View File

@@ -46,6 +46,17 @@ namespace Content.Shared.Chemistry.Reaction
if (!solution.ContainsReagent(reactantName, out var reactantQuantity)) if (!solution.ContainsReagent(reactantName, out var reactantQuantity))
return false; 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; var unitReactions = reactantQuantity / reactantCoefficient;
if (unitReactions < lowestUnitReactions) if (unitReactions < lowestUnitReactions)
@@ -53,7 +64,11 @@ namespace Content.Shared.Chemistry.Reaction
lowestUnitReactions = unitReactions; lowestUnitReactions = unitReactions;
} }
} }
return true;
if (reaction.Quantized)
lowestUnitReactions = (int) lowestUnitReactions;
return lowestUnitReactions > 0;
} }
/// <summary> /// <summary>

View File

@@ -1,7 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes; 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; using Robust.Shared.ViewVariables;
namespace Content.Shared.Kitchen namespace Content.Shared.Kitchen
@@ -19,21 +22,21 @@ namespace Content.Shared.Kitchen
[DataField("name")] [DataField("name")]
private string _name = string.Empty; private string _name = string.Empty;
[DataField("reagents")] [DataField("reagents", customTypeSerializer:typeof(PrototypeIdDictionarySerializer<uint, ReagentPrototype>))]
private readonly Dictionary<string, int> _ingsReagents = new(); private readonly Dictionary<string, uint> _ingsReagents = new();
[DataField("solids")] [DataField("solids", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<uint, EntityPrototype>))]
private readonly Dictionary<string, int> _ingsSolids = new (); private readonly Dictionary<string, uint> _ingsSolids = new ();
[DataField("result")] [DataField("result", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Result { get; } = string.Empty; public string Result { get; } = string.Empty;
[DataField("time")] [DataField("time")]
public int CookTime { get; } = 5; public uint CookTime { get; } = 5;
public string Name => Loc.GetString(_name); public string Name => Loc.GetString(_name);
public IReadOnlyDictionary<string, int> IngredientsReagents => _ingsReagents; public IReadOnlyDictionary<string, uint> IngredientsReagents => _ingsReagents;
public IReadOnlyDictionary<string, int> IngredientsSolids => _ingsSolids; public IReadOnlyDictionary<string, uint> IngredientsSolids => _ingsSolids;
} }
} }

View File

@@ -34,6 +34,14 @@
- id: MonkeyCubeBox - id: MonkeyCubeBox
- id: SprayBottleWater - id: SprayBottleWater
- id: ReagentContainerFlour - 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 - type: entity
id: ClosetJanitorFilled id: ClosetJanitorFilled

View File

@@ -5,6 +5,7 @@
startingInventory: startingInventory:
ButchCleaver: 1 ButchCleaver: 1
KitchenKnife: 5 KitchenKnife: 5
FoodBowlBig: 5
DrinkGlass: 10 DrinkGlass: 10
DrinkMug: 5 DrinkMug: 5
DrinkMugBlack: 2 DrinkMugBlack: 2

View File

@@ -2,14 +2,14 @@
- type: entity - type: entity
name: bowl name: bowl
parent: BaseItem parent: ReagentContainerBase
id: FoodBowlBig id: FoodBowlBig
description: A simple bowl, used for soups and salads. description: A simple bowl, used for soups and salads.
components: components:
- type: SolutionContainerManager - type: SolutionContainerManager
solutions: solutions:
food: food:
maxVol: 20 maxVol: 50 # enough to make cheese in.
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Food/bowl.rsi sprite: Objects/Consumable/Food/bowl.rsi
state: bowl state: bowl
@@ -22,17 +22,11 @@
damage: damage:
types: types:
Blunt: 5 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 - type: Drink
solution: food solution: food
isOpen: true isOpen: true
- type: DrawableSolution - type: DrawableSolution
solution: food solution: food
- type: RefillableSolution
solution: food
- type: Damageable - type: Damageable
damageContainer: Inorganic damageContainer: Inorganic
- type: Destructible - type: Destructible

View File

@@ -337,6 +337,7 @@
description: A thin glass bottle used to store condiments. description: A thin glass bottle used to store condiments.
components: components:
- type: Drink - type: Drink
solution: food
openSounds: openSounds:
collection: pop collection: pop
- type: SolutionContainerManager - type: SolutionContainerManager
@@ -344,6 +345,8 @@
food: food:
maxVol: 30 maxVol: 30
- type: SolutionTransfer - type: SolutionTransfer
canChangeTransferAmount: true
minTransferAmount: 5
maxTransferAmount: 30 maxTransferAmount: 30
- type: Sprite - type: Sprite
netsync: false netsync: false
@@ -480,6 +483,7 @@
description: A smaller glass bottle used to store condiments. description: A smaller glass bottle used to store condiments.
components: components:
- type: Drink - type: Drink
solution: food
openSounds: openSounds:
collection: pop collection: pop
- type: SolutionContainerManager - type: SolutionContainerManager

View File

@@ -214,10 +214,10 @@
- type: SolutionContainerManager - type: SolutionContainerManager
solutions: solutions:
food: food:
maxVol: 20 maxVol: 50 # at least enough for one cheese
reagents: reagents:
- ReagentId: Milk - ReagentId: Milk
Quantity: 20 Quantity: 50
- type: entity - type: entity
parent: ReagentContainerBase parent: ReagentContainerBase
@@ -230,10 +230,10 @@
- type: SolutionContainerManager - type: SolutionContainerManager
solutions: solutions:
food: food:
maxVol: 20 maxVol: 50
reagents: reagents:
- ReagentId: MilkSoy - ReagentId: MilkSoy
Quantity: 20 Quantity: 50
- type: entity - type: entity
parent: ReagentContainerBase parent: ReagentContainerBase

View File

@@ -5,7 +5,7 @@
physicalDesc: fibrous physicalDesc: fibrous
color: "#800000" color: "#800000"
tileReactions: tileReactions:
- !type:CreateEntityReaction - !type:CreateEntityTileReaction
entity: Carpet entity: Carpet
maxOnTileWhitelist: maxOnTileWhitelist:
tags: [ Carpet ] tags: [ Carpet ]
@@ -31,7 +31,7 @@
physicalDesc: buzzy physicalDesc: buzzy
color: "#FFD35D" color: "#FFD35D"
tileReactions: tileReactions:
- !type:CreateEntityReaction - !type:CreateEntityTileReaction
entity: MobBee entity: MobBee
usage: 2 usage: 2
maxOnTile: 2 maxOnTile: 2

View File

@@ -4,19 +4,18 @@
name: cheeseburger recipe name: cheeseburger recipe
result: FoodBurgerCheese result: FoodBurgerCheese
time: 5 time: 5
reagents:
Flour: 5
solids: solids:
FoodBreadPlainSlice: 2 # its not a burger bun, but its good enough
FoodMeat: 1 FoodMeat: 1
FoodCheeseSlice: 1
- type: microwaveMealRecipe - type: microwaveMealRecipe
id: RecipeClownBurger id: RecipeClownBurger
name: clownburger recipe name: clownburger recipe
result: FoodBurgerClown result: FoodBurgerClown
time: 5 time: 5
reagents:
Flour: 5
solids: solids:
FoodBreadPlainSlice: 2
ClothingMaskClown: 1 ClothingMaskClown: 1
# - type: microwaveMealRecipe # - type: microwaveMealRecipe
@@ -34,9 +33,8 @@
name: xenoburger recipe name: xenoburger recipe
result: FoodBurgerXeno result: FoodBurgerXeno
time: 5 time: 5
reagents:
Flour: 5
solids: solids:
FoodBreadPlainSlice: 2
FoodMeatXeno: 1 FoodMeatXeno: 1
#- type: microwaveMealRecipe #- type: microwaveMealRecipe
@@ -60,8 +58,8 @@
name: bread recipe name: bread recipe
result: FoodBreadPlain result: FoodBreadPlain
time: 15 time: 15
reagents: solids:
Flour: 15 FoodDough: 1
# - type: microwaveMealRecipe # - type: microwaveMealRecipe
# id: RecipeSandwich # id: RecipeSandwich
@@ -105,9 +103,8 @@
name: cream cheese bread recipe name: cream cheese bread recipe
result: FoodBreadCreamcheese result: FoodBreadCreamcheese
time: 20 time: 20
reagents:
Flour: 15
solids: solids:
FoodDough: 1
FoodCheeseSlice: 2 FoodCheeseSlice: 2
- type: microwaveMealRecipe - type: microwaveMealRecipe
@@ -115,11 +112,8 @@
name: banana bread recipe name: banana bread recipe
result: FoodBreadBanana result: FoodBreadBanana
time: 25 time: 25
reagents:
Flour: 15
Milk: 5
solids: solids:
FoodEgg: 3 FoodDough: 1
FoodBanana: 1 FoodBanana: 1
#Pizzas #Pizzas
@@ -128,9 +122,8 @@
name: margherita pizza recipe name: margherita pizza recipe
result: FoodPizzaMargherita result: FoodPizzaMargherita
time: 30 time: 30
reagents:
Flour: 10
solids: solids:
FoodDough: 1
FoodCheeseSlice: 4 FoodCheeseSlice: 4
FoodTomato: 1 FoodTomato: 1
@@ -139,9 +132,8 @@
name: mushroom pizza recipe name: mushroom pizza recipe
result: FoodPizzaMushroom result: FoodPizzaMushroom
time: 25 time: 25
reagents:
Flour: 10
solids: solids:
FoodDough: 1
FoodMushroom: 5 FoodMushroom: 5
- type: microwaveMealRecipe - type: microwaveMealRecipe
@@ -149,9 +141,8 @@
name: meat pizza recipe name: meat pizza recipe
result: FoodPizzaMeat result: FoodPizzaMeat
time: 30 time: 30
reagents:
Flour: 10
solids: solids:
FoodDough: 1
FoodMeat: 3 FoodMeat: 3
FoodCheeseSlice: 1 FoodCheeseSlice: 1
FoodTomato: 1 FoodTomato: 1
@@ -161,9 +152,8 @@
name: vegetable pizza recipe name: vegetable pizza recipe
result: FoodPizzaVegetable result: FoodPizzaVegetable
time: 30 time: 30
reagents:
Flour: 10
solids: solids:
FoodDough: 1
FoodEggplant: 1 FoodEggplant: 1
FoodCarrot: 1 FoodCarrot: 1
FoodCorn: 1 FoodCorn: 1
@@ -365,9 +355,8 @@
name: banana cream pie name: banana cream pie
result: FoodPieBananaCream result: FoodPieBananaCream
time: 15 time: 15
reagents:
Flour: 10
solids: solids:
FoodCakeBatter: 1 # should really be pie pastry or whatever. Good enough.
FoodBanana: 3 FoodBanana: 3
#Donks i guess #Donks i guess

View File

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