Modding:Meal Container
Semantically, a meal is an array of food ingredient item stacks it was cooked with. Each item stack in the meal must have the same size, and this becomes the initial serving size of the meal. For example, cooking 1 raw meat in slot 0 and slot 1 of a claypot-burned creates a meal. That meal has a 1x stack of raw meat in slot 0 and a 1x stack of raw meat in slot 1. Slots 2 and 3 are empty.
However, in the VS implemenation, meals are tightly coupled with their containers. Meals never exist independently of their container, neither as collectibles (items/blocks), nor as C# objects. In the implementation, transferring a meal between containers actually involves creating a new container block and then transferring over the ingredients from the source container. Furthermore, meals and meal containers barely use behaviors, with almost all of the logic implemented in Block classes. This limits how much one can customize new meal containers in a mod, without having to resort to coding and subclassing the existing block classes.
This tight coupling of meals and containers contrasts with liquids. Liquids have liquid portion items that are independent of their containers, although the liquid portions are rarely shown in the game. Having a separate liquid portion items creates a clean division between the liquid logic and the liquid container logic, and makes it easier to add new liquid containers.
Viewing cooked meal ingredients
It is possible to demonstrate that the meal data is simply the ingredients that it was made with. All of the cooked meal containers inherit from BlockContainer in some way. While a BlockContainer is in an inventory (as oppsed to existing as a block on the ground outside of a ground storage), its inventory is stored in the contents attribute in the item stack. In creative mode, these attributes can be viewed with .gencraftjson command. Note that the item stack attributes are distinct from the attributes defined in the block's json file. Every block of a given type shares the block attributes, but the item stack attributes can be different for every stack of that block.
For example, this is the output from an apple pie. Notice how it contains the 4 applies it was made with as well as the spelt dough.
{
type: "Block",
code: "game:pie-perfect",
attributes: {
"pieSize": 4,
"topCrustType": 1,
"bakeLevel": 2,
"contents": {
"0": { "type": "item", "code": "dough-spelt", "attributes": { "transitionstate": { } }},
"1": { "type": "item", "code": "fruit-redapple", "attributes": { "transitionstate": { } }},
"2": { "type": "item", "code": "fruit-redapple", "attributes": { "transitionstate": { } }},
"3": { "type": "item", "code": "fruit-redapple", "attributes": { "transitionstate": { } }},
"4": { "type": "item", "code": "fruit-redapple", "attributes": { "transitionstate": { } }},
"5": { "type": "item", "code": "dough-spelt", "attributes": { "transitionstate": { } }}
}
}
}
As another example, here is a bowl-meal holding a "Red meat stew with boiled cranberries."
{
type: "Block",
code: "game:bowl-meal",
attributes: {
"contents": {
"0": {
"type": "item",
"code": "redmeat-raw",
"attributes": { "transitionstate": { "createdTotalHours": 883.6644722075278, "lastUpdatedTotalHours": 904.0225999776667, "freshHours": [144], "transitionHours": [36], "transitionedHours": [13.331751] }, "temperature": { "temperatureLastUpdate": 898.3115222173333, "temperature": 0 } }
},
"1": {
"type": "item",
"code": "redmeat-raw",
"attributes": { "transitionstate": { "createdTotalHours": 883.6644722075278, "lastUpdatedTotalHours": 904.0225999776667, "freshHours": [144], "transitionHours": [36], "transitionedHours": [13.331751] }, "temperature": { "temperatureLastUpdate": 898.3115222173333, "temperature": 0 } }
},
"2": {
"type": "item",
"code": "fruit-cranberry",
"attributes": { "transitionstate": { "createdTotalHours": 883.6644722075278, "lastUpdatedTotalHours": 904.0225999776667, "freshHours": [144], "transitionHours": [36], "transitionedHours": [13.331751] }, "temperature": { "temperatureLastUpdate": 898.3115222173333, "temperature": 0 } }
}
},
"recipeCode": "meatystew",
"quantityServings": 1
}
}
Meal containers blocks
Most of the cooking containers have a raw form, empty container form (sometimes called a cooking block in the game), and container with meal form (sometimes called a cooked block in the game).
Raw | Empty container | Container with meal |
---|---|---|
bowl-raw | bowl-fired | bowl-meal |
crock-raw-* | crock-burned-* | crock-burned-* |
claypot-raw | claypot-burned | claypot-cooked |
- | dirtyclaypot-empty | dirtyclaypot-cooked |
pie-raw | - | pie-partbaked, pie-perfect, pie-charred |
All of the meal containers are implemented as blocks. Many of the contains have comments like this in their json file, stating that they are only blocks for legacy reasons:
{ name: "Unplaceable", "__comment": "The ground storable obsoletes this being a block. Should be an item, but is kept a block for backwards compatibility" }
The defining characteristic of a block, relative to an item, is that blocks can be placed in the world. However, around version 1.16, the Unplaceable behavior and GroundStorable behavior were added to all of the meal containers (except for pie-* which is still directly placed on the ground). Now when the block is right clicked onto the ground, instead of directly setting the block at that location to the meal container's id, a groundstorage block is created that contains the block in inventory form. The meal containers may still exist as placed blocks in the world, if they were placed before version 1.16, and the world was upgraded.
Despite the containers being blocks for legacy reasons, any new meal containers introduced by mods must be blocks, because BlockCookedContainerBase requires the serving target to be a block. Specifically some methods in BlockCookedContainerBase directly look at the Itemstack.Block field on the serving target instead of also checking Itemstack.Item or using Itemstack.Collectible.
Regardless, the block entities of the meal containers can largely be ignored, because the meal containers typically only exist in item stacks (which do not have block entities). A block entity is only created if the block is directly placed in the world (storing it in a container like ground storage doesn't count). So the only meal container block entities are pies, and legacy block entities from pre 1.16 worlds.
Meal container block classses
This is the class hierachy for the meal container block classes.
- Block
-
- BlockContainer
-
- BlockMeal
- Features
- Right click to eat while holding in the hotbar.
- After eating, it is replaced with the block described in the eatenBlock attribute, or destroyed if the attribute is unset.
- Used by
- bowl-meal
- BlockPie
- Features
- Uses the pieSize item stack attribute to set the number of slices remaining
- Allows baking raw pies in an oven
- Reduces the spoilage rate of the raw ingredients
- Handles rendering full pies and sliced pies by calling MealMeshCache.GetPieMesh
- Prevents eating raw pies and pies with more than a single slice
- Reduces the nutrition of partbaked and charred pies based on the nutritionMul attribute.
- Used by
- pie-raw
- pie-partbaked
- pie-perfect
- pie-charred
- BlockCookedContainerBase
- Features
- Supports serving from this block (serving source) on the ground into empty meal containers. Specifically, the cooked container intercepts the right click event while it is inside of a groundstorage block. If the current item in the hotbar is an empty meal container, then the contents are served from the container on the ground into the container in the hotbar.
- BlockCookedContainer
- Features
- Handles serving from this block in the hotbar into empty meal containers.
- Used by
- dirtyclaypot-cooked
- claypot-cooked
- BlockCrock
- Features
- Reduces the spoilage rate by overriding GetContainingTransitionModifierContained
- Used by
- crock-burned-*
- BlockCookingContainer
- Features
- Allows cooking meals in a firepit, if the ingredients match a cooking recipe
- Used by
- claypot-burned
Serving between containers
Serving means transferring a meal from a source container to an empty target meal container. In order for the target to be recognized as an empty meal container, it must have the mealContainer=true block attribute. Also the mealBlockCode attribute must be set to a valid block code. The non-empty meal container must implement IBlockMealContainer, because that interface is used to fill the meal container.
BlockCookedContainerBase has the logic to allow it to be the serving source, when it is inside of a groundstorage block, and the serving target is the active block in the hotbar. BlockCookedContainer has the logic to allow it to be the serving source, when it is active in the hotbar, and the serving target is on the ground.
So for example, when holding a bowl-fired (serving target), it can serve from a claypot-cooked (serving source) on the ground, because the claypot-cooked's class derives from BlockCookedContainerBase, and the bowl-fired has mealContainer=true and mealBlockCode set.
Similarly, when holding a claypot-cooked (serving source), it can serve into a bowl-fired (serving target) on the ground, because the claypot-cooked's class is BlockCookedContainer.
Nothing can serve from a bowl-meal, because the bowl-meal's class, BlockMeal, does not inherit from BlockCookedContainerBase. Likewise, nothing can serve from a pie, because its class also does not inherit from BlockCookedContainerBase.
Meal nutrition gain
Meals have a nutrition boost comapred to eating their raw ingredients. This nutrition boost comes from the difference between the nutritionProps and nutritionPropsWhenInMeal fields for all of the ingredients. So the exact boost depends on each ingredient. Although all of the ingredients in the base game get a boost from putting them in the meal, it would be possible to make an ingredient that gets a debuff from putting it in a meal.
For example, the nutrition of red meat is boosted by 140, because redmeat-cooked sets nutritionProps to { satiety: 280, health: 0, foodcategory: "Protein" }
and the nutritionPropsWhenInMeal to { satiety: 420, foodcategory: "Protein" }
.
Code Modding | |||||||
---|---|---|---|---|---|---|---|
Basics | Code Mods • Preparing For Code Mods • Creating A Code Mod | ||||||
Tutorials |
|
||||||
Advanced | Server-Client Considerations • Setting up your Development Environment • Advanced Blocks • Advanced Items • Block and Item Interactions • Block Behavior • Block Entity • Particle Effects • World Access • Inventory Handling • Commands • GUIs • Network API • Monkey patching (Harmony) | ||||||
Data Management | VCDBS format • Savegame Moddata • ModConfig File • Chunk Moddata • Serialization Formats • TreeAttribute | ||||||
Worldgen | WorldGen API • NatFloat • EvolvingNatFloat | ||||||
Rendering | Shaders and Renderers |
Wondering where some links have gone?
The modding navbox is going through some changes! Check out Navigation Box Updates for more info and help finding specific pages.
Modding | |
---|---|
Modding Introduction | Getting Started • Theme Pack |
Content Modding | Content Mods • Developing a Content Mod • Basic Tutorials • Intermediate Tutorials • Advanced Tutorials • Content Mod Concepts |
Code Modding | Code Mods • Setting up your Development Environment |
Property Overview | Item • Entity • Block • Block Behaviors • Block Classes • Block Entities • Block Entity Behaviors • World properties |
Workflows & Infrastructure | Modding Efficiency Tips • Mod-engine compatibility • Mod Extensibility • VS Engine |
Additional Resources | Community Resources • Modding API Updates • Programming Languages • List of server commands • List of client commands • Client startup parameters • Server startup parameters Example Mods • API Docs • GitHub Repository |