diff --git a/ItemsAdder/tfmc_cooking/contents/categories.yml b/ItemsAdder/tfmc_cooking/contents/categories.yml index 6238fc4..5e06c3d 100644 --- a/ItemsAdder/tfmc_cooking/contents/categories.yml +++ b/ItemsAdder/tfmc_cooking/contents/categories.yml @@ -280,6 +280,11 @@ categories: - tfmc_cooking:cutting_knife - tfmc_cooking:flour - tfmc_cooking:flour_rotten + - tfmc_cooking:flour_heap + - tfmc_cooking:flour_heap_rotten + - tfmc_cooking:roux + - tfmc_cooking:roux_burnt + - tfmc_cooking:roux_rotten - tfmc_cooking:flour_model - tfmc_cooking:flour_rotten_model - tfmc_cooking:yeast diff --git a/ItemsAdder/tfmc_cooking/contents/intermediates.yml b/ItemsAdder/tfmc_cooking/contents/intermediates.yml index 071b9bd..a828be4 100644 --- a/ItemsAdder/tfmc_cooking/contents/intermediates.yml +++ b/ItemsAdder/tfmc_cooking/contents/intermediates.yml @@ -43,6 +43,42 @@ items: generate: false model_path: item/intermediate/flour_rotten + # Thick sauce: the heap flour shows as in the frying pan, and the roux it cooks into there. + flour_heap: + display_name: "Flour Heap" + resource: + material: PAPER + generate: false + model_path: item/intermediate/flour_heap + + flour_heap_rotten: + display_name: "Flour Heap Musty" + resource: + material: PAPER + generate: false + model_path: item/intermediate/flour_heap_rotten + + roux: + display_name: "Roux" + resource: + material: PAPER + generate: false + model_path: item/intermediate/roux + + roux_burnt: + display_name: "Roux Burnt" + resource: + material: PAPER + generate: false + model_path: item/intermediate/roux_burnt + + roux_rotten: + display_name: "Roux Rotten" + resource: + material: PAPER + generate: false + model_path: item/intermediate/roux_rotten + yeast: display_name: "Yeast" resource: diff --git a/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/flour_heap.json b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/flour_heap.json new file mode 100644 index 0000000..b45a81c --- /dev/null +++ b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/flour_heap.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "tfmc_cooking:item/intermediate/flour_heap" + } +} diff --git a/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/flour_heap_rotten.json b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/flour_heap_rotten.json new file mode 100644 index 0000000..a206bbd --- /dev/null +++ b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/flour_heap_rotten.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "tfmc_cooking:item/intermediate/flour_heap_rotten" + } +} diff --git a/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/roux.json b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/roux.json new file mode 100644 index 0000000..9d05bba --- /dev/null +++ b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/roux.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "tfmc_cooking:item/intermediate/roux" + } +} diff --git a/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/roux_burnt.json b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/roux_burnt.json new file mode 100644 index 0000000..a7c555c --- /dev/null +++ b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/roux_burnt.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "tfmc_cooking:item/intermediate/roux_burnt" + } +} diff --git a/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/roux_rotten.json b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/roux_rotten.json new file mode 100644 index 0000000..b3ba8ff --- /dev/null +++ b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/models/item/intermediate/roux_rotten.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "tfmc_cooking:item/intermediate/roux_rotten" + } +} diff --git a/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/flour_heap.png b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/flour_heap.png new file mode 100644 index 0000000..883414c Binary files /dev/null and b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/flour_heap.png differ diff --git a/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/flour_heap_rotten.png b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/flour_heap_rotten.png new file mode 100644 index 0000000..bb4e5b7 Binary files /dev/null and b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/flour_heap_rotten.png differ diff --git a/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/roux.png b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/roux.png new file mode 100644 index 0000000..9bff9bc Binary files /dev/null and b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/roux.png differ diff --git a/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/roux_burnt.png b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/roux_burnt.png new file mode 100644 index 0000000..a8dac48 Binary files /dev/null and b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/roux_burnt.png differ diff --git a/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/roux_rotten.png b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/roux_rotten.png new file mode 100644 index 0000000..07ca8fd Binary files /dev/null and b/ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking/textures/item/intermediate/roux_rotten.png differ diff --git a/README.md b/README.md index 6678a06..9551e34 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Cooking makes food an interactive part of everyday life. Players grow and gather - **Pizzas** — place a pizza peel, put dough on it, spread a paste and add cheese, vegetables, fruit, sausage and spices, then carry the peel into a lit oven to bake. A baked pizza is taken off a slice at a time. Its toppings name it, and its food, quality and freshness come from its layers (`pizzas.yml`). Its toppings also earn it bonuses such as Saucy, Extra Cheesy or Well Seasoned (`bonuses` in `pizzas.yml`). - **Salads** — right-click a placed bowl with chopped or cooked veg, fruit, cheese or meat, add a dressing or seasoning, and toss it with an empty hand. Each ingredient lies in the bowl as a small heap of its pieces, in its own look for every origin and state (`models` and `spread-radius`); the tossed salad stays in the bowl with them and is eaten from it. Its ingredients name it, its kind (fruit, greek, carrot, mixed) gives it its look, and it earns bonuses such as Dressed or Crisp (`salads.yml`). - **Stock and mashed potatoes** — boil two bones in a cup of water in the saucepan and bottle the stock (`condiments.yml`); a soup started with stock instead of water is Rich. Put four boiled cut potatoes and a butter (with milk or salt if you like) in an empty pot, mash them without water or heat, and ladle the mash onto a plate beside the other food (`pot.yml`). +- **Thick sauce** — cook flour in a buttered frying pan until it turns into a roux (without butter it burns, and a burnt roux spoils a sauce). A roux put into a saucepan of water, milk or a brew takes one of its two ingredient places and makes the sauce Thick: the tag names it (Thick Garlic Sauce), so a dish it is ladled onto lists a Thick sauce too. Raw flour is refused with a hint (`types.yml` flour `output`, `naming.yml` `thickener`, `tags.yml` `sauce_thickness`). - **Pies** — line a pie tin with dough, fill it with fruit, meat, mashed potato, vegetables or cheese, and bake it in the oven like the bread tray. The whole pie comes out of the tin with an empty hand and is cut into four slices on the cutting board. Its fillings make its kind (Apple, Key Lime, Mixed Berry, Pumpkin, Meat, Shepherd's) and name, and its food, quality and freshness come from its dough and fillings (`pies.yml`). - **Sweet berries, sugar cane and beetroot** — sweet berries become Cooking fruit on pickup (`conversions.yml`), so they go into salads, pies (Mixed Berry) and jam sandwiches (Sweet Berry Jam). The milling stone holds several recipes (`milling-recipes.yml`): 8 wheat grind into flour, and 8 sugar cane into 8 sugar, the same sweetener vanilla sugar becomes on pickup; each recipe sets its result's category, origin and amount, what the stone shows, and what a broken stone gives back. A soup that is at least half beetroot is ladled as Borscht: `pot.yml` `kinds` name a soup from its boiled pieces, as `pies.yml` kinds name pies. - **Pumpkin** — a pie of nothing but cut or chopped pumpkin bakes into a Pumpkin Pie (`pies.yml` kinds), and a soup that is at least half pumpkin is ladled as Pumpkin Soup (`pot.yml` kinds). diff --git a/src/main/java/net/tfminecraft/cooking/cache/NamingConfig.java b/src/main/java/net/tfminecraft/cooking/cache/NamingConfig.java index cead0c9..7610a35 100644 --- a/src/main/java/net/tfminecraft/cooking/cache/NamingConfig.java +++ b/src/main/java/net/tfminecraft/cooking/cache/NamingConfig.java @@ -25,6 +25,7 @@ private static Map> defaultCategories() { map.put("sweetener", Set.of("sweetener")); map.put("addon", Set.of("garnish", "spice")); map.put("dough-filler", Set.of("fruit")); + map.put("thickener", Set.of("thickener")); return map; } diff --git a/src/main/java/net/tfminecraft/cooking/cooking/CookingReference.java b/src/main/java/net/tfminecraft/cooking/cooking/CookingReference.java index 106e1fd..ac1318f 100644 --- a/src/main/java/net/tfminecraft/cooking/cooking/CookingReference.java +++ b/src/main/java/net/tfminecraft/cooking/cooking/CookingReference.java @@ -306,7 +306,8 @@ public List getIngredients() { String category = fi.getCategory().toLowerCase(); - if (NamingConfig.isCategory("seasoning", category)) { + // A seasoning or a thickener (a roux) doesn't name the dish; Seasoned and Thick tags say it's there. + if (NamingConfig.isCategory("seasoning", category) || NamingConfig.isCategory("thickener", category)) { continue; } diff --git a/src/main/java/net/tfminecraft/cooking/cooking/SauceReference.java b/src/main/java/net/tfminecraft/cooking/cooking/SauceReference.java index 02b9ede..2512582 100644 --- a/src/main/java/net/tfminecraft/cooking/cooking/SauceReference.java +++ b/src/main/java/net/tfminecraft/cooking/cooking/SauceReference.java @@ -216,12 +216,26 @@ public boolean canAdd(Player player, ItemStack i) { + if (StationAddonRules.isThickenerCategory(category)) { + + return StationAddonRules.canAcceptThickener(slots, fi, player); + + } + + + if (category.equals("alcohol")) return true; // What a bottling recipe for this liquid still needs (tomato paste in vinegar for catsup). - return bottlingTakes(fi); + if (bottlingTakes(fi)) return true; + + // Raw flour doesn't thicken a sauce; a roux does. + if (ItemCache.isFlour(i) && player != null) { + player.sendMessage("§7Cook the flour in a buttered frying pan first: a roux thickens the sauce."); + } + return false; } @@ -241,26 +255,10 @@ public void scoop(Player p, ItemStack ladle) { - int flourCount = 0; - boolean hasMilk = false; - for (FoodItem fi : slots.values()) { - - String category = fi.getCategory().toLowerCase(); - - if (category.equals("flour")) { - - flourCount++; - - } - - } - - - for (String c : colours) { // A white brew (vodka) is not milk. @@ -284,21 +282,8 @@ public void scoop(Player p, ItemStack ladle) { StationAddonRules.applySweetTag(sauce, slots); - - - TagTrack thickTrack = new TagTrack(TrackLoader.getByString("sauce_thickness")); - - if (flourCount >= 1) { - - thickTrack.setValue(1); - - } else { - - thickTrack.setValue(0); - - } - - sauce.addOrModifyTrack(thickTrack); + // A roux makes it Thick, which also names it (naming.yml prefix-tracks), so dishes it's ladled onto say so. + StationAddonRules.applyThickTag(sauce, slots); diff --git a/src/main/java/net/tfminecraft/cooking/utils/StationAddonRules.java b/src/main/java/net/tfminecraft/cooking/utils/StationAddonRules.java index 3094ab6..46e5ace 100644 --- a/src/main/java/net/tfminecraft/cooking/utils/StationAddonRules.java +++ b/src/main/java/net/tfminecraft/cooking/utils/StationAddonRules.java @@ -8,6 +8,7 @@ import org.bukkit.entity.Player; import net.tfminecraft.cooking.cache.NamingConfig; +import net.tfminecraft.cooking.enums.Tag; import net.tfminecraft.cooking.item.FoodItem; import net.tfminecraft.cooking.item.tag.TagTrack; import net.tfminecraft.cooking.loader.TrackLoader; @@ -34,6 +35,54 @@ public static boolean isSweetenerCategory(String category) { return NamingConfig.isCategory("sweetener", category); } + /** A thickener (naming.yml categories: thickener), a roux cooked from flour in a buttered frying pan. */ + public static boolean isThickenerCategory(String category) { + return NamingConfig.isCategory("thickener", category); + } + + public static boolean hasThickener(Map slots) { + for (FoodItem item : slots.values()) { + if (item != null && isThickenerCategory(item.getCategory())) { + return true; + } + } + return false; + } + + /** One thickener per batch, and never a burnt one. */ + public static boolean canAcceptThickener(Map slots, FoodItem incoming, Player player) { + if (incoming == null || !isThickenerCategory(incoming.getCategory())) { + return false; + } + String refusal = null; + if (incoming.hasTag(Tag.BURNT)) { + refusal = "§cThat roux is burnt. It would spoil the sauce."; + } else if (hasThickener(slots)) { + refusal = "§cThere's already a roux in there."; + } + if (refusal == null) { + return true; + } + if (player != null) { + player.sendMessage(refusal); + } + return false; + } + + /** A thickener makes the sauce Thick (tags.yml sauce_thickness); without one the sauce gets no thickness tag. */ + public static void applyThickTag(FoodItem product, Map slots) { + if (!hasThickener(slots)) { + return; + } + TagTrack track = TrackLoader.getByString("sauce_thickness"); + if (track == null) { + return; + } + track = new TagTrack(track); + track.setValue(1); + product.addOrModifyTrack(track); + } + public static boolean hasSweetener(Map slots) { for (FoodItem item : slots.values()) { if (item != null && isSweetenerCategory(item.getCategory())) { diff --git a/src/main/resources/composition.yml b/src/main/resources/composition.yml index b08153e..33b2e6f 100644 --- a/src/main/resources/composition.yml +++ b/src/main/resources/composition.yml @@ -24,6 +24,7 @@ context-overrides: - pepper - sweetener - flour + - thickener SOUP_SCOOP: extra: - salt diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 4050983..a4a9431 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -164,6 +164,7 @@ colours: - origin.pistachio 93c47d - origin.rhubarb c45c6a - origin.garlic f5f0e6 + - origin.roux d9a95b dictionary: @@ -180,6 +181,7 @@ dictionary: sauce: "#997950Sauce" garnish: "#0b5394Garnish" liquid: "#3d85c6Liquid" + thickener: "#d9a95bThickener" snack: "#e8c56bSnack" sauce-dict: diff --git a/src/main/resources/models.yml b/src/main/resources/models.yml index 86392d3..c4c6d45 100644 --- a/src/main/resources/models.yml +++ b/src/main/resources/models.yml @@ -3116,6 +3116,17 @@ melon_slice: y: -0.25 flour: + # In the frying pan (types.yml flour's roux output) flour shows as a flat heap, placed like the roux it becomes. + display-data-furniture: + frying_pan: + rotation: + x: 90 + scale: + x: 0.45 + y: 0.45 + z: 0.45 + position: + y: -0.26 fresh: weight: 1 tags: @@ -3124,6 +3135,8 @@ flour: item: ia.tfmc_cooking:flour display: item: ia.tfmc_cooking:flour_model + furniture: + - frying_pan ia.tfmc_cooking:flour_heap stale: weight: 1 tags: @@ -3132,6 +3145,8 @@ flour: item: ia.tfmc_cooking:flour display: item: ia.tfmc_cooking:flour_model + furniture: + - frying_pan ia.tfmc_cooking:flour_heap rotten: weight: 1 tags: @@ -3140,6 +3155,44 @@ flour: item: ia.tfmc_cooking:flour_rotten display: item: ia.tfmc_cooking:flour_rotten_model + furniture: + - frying_pan ia.tfmc_cooking:flour_heap_rotten + +# Roux (types.yml roux), flour cooked in a buttered frying pan. In the pan it lies flat like popcorn, lowered to the pan +# floor, at 45% so two round puddles fit side by side. +roux: + display-data-furniture: + frying_pan: + rotation: + x: 90 + scale: + x: 0.45 + y: 0.45 + z: 0.45 + position: + y: -0.26 + cooked: + weight: 1 + gui: + item: ia.tfmc_cooking:roux + display: + item: ia.tfmc_cooking:roux + burnt: + weight: 2 + tags: + - burnt + gui: + item: ia.tfmc_cooking:roux_burnt + display: + item: ia.tfmc_cooking:roux_burnt + rotten: + weight: 3 + tags: + - rotten + gui: + item: ia.tfmc_cooking:roux_rotten + display: + item: ia.tfmc_cooking:roux_rotten yeast: fresh: diff --git a/src/main/resources/naming.yml b/src/main/resources/naming.yml index c79a437..d5fe777 100644 --- a/src/main/resources/naming.yml +++ b/src/main/resources/naming.yml @@ -8,6 +8,8 @@ prefix-tracks: - topping_sugar - topping_salt - topping_caramel + # A sauce with a roux: Thick Garlic Sauce, which is also how a dish it's ladled onto lists it. + - sauce_thickness # Max filler origins shown in a composed name fillers: @@ -37,6 +39,9 @@ categories: - spice dough-filler: - fruit + # What thickens a sauce in the saucepan (one per batch): a roux. + thickener: + - thickener # Per-category tag step display overrides (fallback after per-type tag-labels) tag-label-defaults: diff --git a/src/main/resources/tags.yml b/src/main/resources/tags.yml index eea880f..7de8b55 100644 --- a/src/main/resources/tags.yml +++ b/src/main/resources/tags.yml @@ -124,6 +124,7 @@ sauce_creamy: value: 0 nutrition-mult: 1.1 food-mult: 1.05 +# A sauce with a roux in it (SauceReference) is Thick; without one it gets no thickness tag. Thin is kept for food strings. sauce_thickness: thin: name: "#bfb999Thin" diff --git a/src/main/resources/types.yml b/src/main/resources/types.yml index e242da7..816a5dc 100644 --- a/src/main/resources/types.yml +++ b/src/main/resources/types.yml @@ -494,6 +494,16 @@ flour: model: flour food: 4 nutrition: 4 + # Thick sauce: flour cooked in a buttered frying pan becomes a roux (the butter goes into it, as in any frying). + # Without butter it burns, and a burnt roux won't go in a sauce. Flour elsewhere keeps its values: a type whose only + # options have an output isn't raw until it's in the pan. The pan's slot whitelist (InteractibleFurniture) must let + # flour in: c.grain(type=flour). + cooking-options: + frying_pan: + tag: 1 + time: 12 + burn: 30 + output: thickener(type=roux;origin=Roux;tags=processed.0) age: warmth: 450 freshness: 20 @@ -607,6 +617,23 @@ sauce: food: 6 nutrition: 5 +###Roux: flour cooked in a buttered frying pan (flour's frying_pan output). Added to a saucepan of water, milk or a brew +# it takes one of the two ingredient places and makes the sauce Thick (naming.yml categories: thickener). It doesn't +# name the sauce; the Thick tag does: Thick Garlic Sauce. +roux: + name: "#d9a95bRoux" + model: roux + category: thickener + food: 6 + nutrition: 4 + age: + warmth: 450 + freshness: 14 + tag-labels: + freshness: + stale: "#c3db81Rancid" + rotten: "#8B7355Spoiled" + ##SOUP soup: age: diff --git a/src/test/java/net/tfminecraft/cooking/cooking/CookingReferencesTest.java b/src/test/java/net/tfminecraft/cooking/cooking/CookingReferencesTest.java index 89b54ce..a9fe966 100644 --- a/src/test/java/net/tfminecraft/cooking/cooking/CookingReferencesTest.java +++ b/src/test/java/net/tfminecraft/cooking/cooking/CookingReferencesTest.java @@ -403,12 +403,12 @@ void stackedSauceContainersReturnExtrasOrDropThemWhenTheInventoryIsFull(String m } @Test - void sauceScoopCombinesQualityAndFlourMilkTagsThenClearsTheStation() { + void sauceScoopCombinesQualityAndRouxMilkTagsThenClearsTheStation() { SauceReference ref = new SauceReference(env.furniture, Method.SAUCEPAN); ref.scoop(env.player, new ItemStack(Material.PAPER)); env.player.getInventory().setItemInMainHand(env.stack(env.food("milk", "milk", "Milk"), Material.WHITE_DYE, 1)); ref.interact(env.interaction()); - ref.slots.put("input_1", env.food("flour", "flour", "Wheat")); + ref.slots.put("input_1", env.food("roux", "thickener", "Roux")); ref.slots.put("input_2", env.food("sugar", "sweetener", "Sugar")); ref.colours.add("ffffff"); ref.scoop(env.player, new ItemStack(Material.PAPER)); @@ -424,7 +424,7 @@ void sauceScoopCombinesQualityAndFlourMilkTagsThenClearsTheStation() { ref.secondaries.put("liquid", -1); ref.colours.add("aa5500"); ref.scoop(env.player, new ItemStack(Material.PAPER)); - assertEquals(0, env.lastBuilt.getTagTrack("sauce_thickness").getValue()); + assertFalse(env.lastBuilt.hasTagTrack("sauce_thickness")); assertFalse(env.lastBuilt.hasTagTrack("sauce_creamy")); } diff --git a/src/test/java/net/tfminecraft/cooking/cooking/SauceThickeningTest.java b/src/test/java/net/tfminecraft/cooking/cooking/SauceThickeningTest.java new file mode 100644 index 0000000..75d1dcb --- /dev/null +++ b/src/test/java/net/tfminecraft/cooking/cooking/SauceThickeningTest.java @@ -0,0 +1,181 @@ +package net.tfminecraft.cooking.cooking; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import net.tfminecraft.cooking.cache.ItemCache; +import net.tfminecraft.cooking.cache.NamingConfig; +import net.tfminecraft.cooking.cooking.CookingReferencesTest.Environment; +import net.tfminecraft.cooking.enums.Method; +import net.tfminecraft.cooking.item.FoodItem; +import net.tfminecraft.cooking.loader.FoodLoader; +import net.tfminecraft.cooking.loader.NamingLoader; +import net.tfminecraft.cooking.loader.TrackLoader; +import net.tfminecraft.cooking.utils.StationAddonRules; + +/** Thick sauce: a roux (flour cooked in a buttered frying pan) in the saucepan makes the sauce Thick and names it so. */ +class SauceThickeningTest { + static final String THICK_MESSAGE_BURNT = "§cThat roux is burnt. It would spoil the sauce."; + static final String THICK_MESSAGE_TWICE = "§cThere's already a roux in there."; + + Environment env; + SauceReference pan; + + @BeforeEach void open() { + env = new Environment(); + pan = new SauceReference(env.furniture, Method.SAUCEPAN); + // As naming.yml ships it: Sweet ... Thick prefixes, and the default categories (which include thickener). + NamingConfig.apply(List.of("sweet", "sauce_thickness"), 3, 2, 2, null, null, null); + } + + @AfterEach void close() { + NamingConfig.apply(List.of("sweet"), 3, 2, 2, null, null, null); + env.close(); + } + + FoodItem roux() { + return env.food("roux", "thickener", "Roux"); + } + + void hold(FoodItem food, Material material, int amount) { + env.player.getInventory().setItemInMainHand(env.stack(food, material, amount)); + } + + void pourMilk() { + hold(env.food("milk", "dairy", "Milk"), Material.WHITE_DYE, 1); + pan.interact(env.interaction()); + assertEquals(-1, pan.getSecondary("liquid")); + } + + @SuppressWarnings("deprecation") + String heldName() { + return env.player.getInventory().getItemInMainHand().getItemMeta().getDisplayName(); + } + + @Test void aRouxTakesOneOfTheTwoPlacesAndMakesAThickSauceThatItDoesNotName() { + pourMilk(); + hold(roux(), Material.BROWN_DYE, 2); + pan.interact(env.interaction()); + assertEquals(1, pan.getSlots().size(), "The roux takes one place"); + assertEquals(1, env.player.getInventory().getItemInMainHand().getAmount()); + hold(env.food("garlic", "garnish", "Garlic"), Material.GREEN_DYE, 1); + pan.interact(env.interaction()); + assertEquals(2, pan.getSlots().size(), "The other place is still free for an addition"); + + env.player.getInventory().setItemInMainHand(new ItemStack(Material.PAPER)); + pan.interact(env.interaction()); + FoodItem sauce = env.lastBuilt; + assertEquals(1, sauce.getTagTrack("sauce_thickness").getValue()); + assertTrue(sauce.getTagTrack("sauce_thickness").getCurrentStep().getName().endsWith("Thick")); + String name = heldName(); + assertTrue(name.contains("Thick Garlic"), name); + assertFalse(name.contains("Roux"), "A thickener doesn't name the sauce: " + name); + assertTrue(pan.isEmpty()); + } + + @Test void aSauceWithoutARouxHasNoThicknessAndNoThinInItsName() { + pourMilk(); + env.player.getInventory().setItemInMainHand(new ItemStack(Material.PAPER)); + pan.interact(env.interaction()); + assertFalse(env.lastBuilt.hasTagTrack("sauce_thickness")); + assertFalse(heldName().contains("Thin"), heldName()); + } + + @Test void aSecondRouxAndABurntRouxAreRefusedWithAReason() { + pourMilk(); + FoodItem burnt = env.food("roux", "thickener", "Roux", Method.FRYING_PAN); + burnt.getTagTrack("cooked").setValue(2); + hold(burnt, Material.BROWN_DYE, 1); + pan.interact(env.interaction()); + assertTrue(pan.getSlots().isEmpty()); + verify(env.player).sendMessage(THICK_MESSAGE_BURNT); + + hold(roux(), Material.BROWN_DYE, 2); + pan.interact(env.interaction()); + pan.interact(env.interaction()); + assertEquals(1, pan.getSlots().size()); + assertEquals(1, env.player.getInventory().getItemInMainHand().getAmount()); + verify(env.player).sendMessage(THICK_MESSAGE_TWICE); + } + + @Test void rawFlourIsRefusedWithAHintAndOtherFoodSilently() { + pourMilk(); + env.cache.when(() -> ItemCache.isFlour(any())).thenAnswer(inv -> Environment.material(inv.getArgument(0), Material.SUGAR)); + hold(env.food("flour", "grain", "Wheat"), Material.SUGAR, 1); + pan.interact(env.interaction()); + assertTrue(pan.getSlots().isEmpty()); + verify(env.player).sendMessage("§7Cook the flour in a buttered frying pan first: a roux thickens the sauce."); + assertFalse(pan.canAdd(null, env.stack(env.food("flour", "grain", "Wheat"), Material.SUGAR, 1))); + + hold(env.food("bread", "bread", "Wheat"), Material.BREAD, 1); + pan.interact(env.interaction()); + assertTrue(pan.getSlots().isEmpty()); + verify(env.player, times(1)).sendMessage(anyString()); + } + + @Test void thickenerRulesStandAloneAndWithoutTheirTrack() { + Map slots = new HashMap<>(); + slots.put("empty", null); + assertFalse(StationAddonRules.hasThickener(slots)); + assertFalse(StationAddonRules.canAcceptThickener(slots, null, null)); + assertFalse(StationAddonRules.canAcceptThickener(slots, env.food("salt", "salt", "Salt"), null)); + FoodItem burnt = env.food("roux", "thickener", "Roux", Method.FRYING_PAN); + burnt.getTagTrack("cooked").setValue(2); + assertFalse(StationAddonRules.canAcceptThickener(slots, burnt, null)); + assertTrue(StationAddonRules.canAcceptThickener(slots, roux(), null)); + + FoodItem sauce = env.food("sauce", "sauce", "Mixed"); + StationAddonRules.applyThickTag(sauce, slots); + assertFalse(sauce.hasTagTrack("sauce_thickness")); + slots.put("input_1", roux()); + assertTrue(StationAddonRules.hasThickener(slots)); + TrackLoader.oList.removeIf(track -> track.getId().equals("sauce_thickness")); + StationAddonRules.applyThickTag(sauce, slots); + assertFalse(sauce.hasTagTrack("sauce_thickness"), "No sauce_thickness track in tags.yml leaves the sauce as it is"); + } + + @Test void theShippedConfigCooksFlourIntoARouxThatThickensAndNamesSauces() throws Exception { + new FoodLoader().load(new File("src/main/resources/types.yml")); + FoodItem flour = new FoodItem(FoodLoader.getByString("flour")); + assertEquals("thickener(type=roux;origin=Roux;tags=processed.0)", + flour.getCookData().getParameters().get(Method.FRYING_PAN).getOutput()); + assertFalse(flour.hasTagTrack("cooked"), "Flour keeps its values outside the pan"); + FoodItem roux = FoodLoader.getByString("roux"); + assertTrue(roux.getCookData().getParameters().isEmpty(), "A roux doesn't cook on"); + + new NamingLoader().load(new File("src/main/resources/naming.yml")); + assertTrue(StationAddonRules.isThickenerCategory("thickener")); + assertTrue(NamingConfig.getPrefixTracks().contains("sauce_thickness")); + assertTrue(StationAddonRules.isThickenerCategory("THICKENER")); + + String models = Files.readString(Path.of("src/main/resources/models.yml")); + String composition = Files.readString(Path.of("src/main/resources/composition.yml")); + String items = Files.readString(Path.of("ItemsAdder/tfmc_cooking/contents/intermediates.yml")); + String categories = Files.readString(Path.of("ItemsAdder/tfmc_cooking/contents/categories.yml")); + assertTrue(models.contains("\nroux:\n")); + assertTrue(models.contains(" - frying_pan ia.tfmc_cooking:flour_heap\n")); + assertTrue(composition.contains(" - flour\n - thickener\n"), "A roux is an extra in a sauce's stars"); + for (String id : List.of("roux", "roux_burnt", "roux_rotten", "flour_heap", "flour_heap_rotten")) { + assertTrue(models.contains("ia.tfmc_cooking:" + id + "\n"), id); + assertTrue(items.contains("\n " + id + ":\n"), id); + assertTrue(categories.contains(" - tfmc_cooking:" + id + "\n"), id); + Path pack = Path.of("ItemsAdder/tfmc_cooking/resourcepack/assets/tfmc_cooking"); + assertTrue(Files.exists(pack.resolve("models/item/intermediate/" + id + ".json")), id); + assertTrue(Files.exists(pack.resolve("textures/item/intermediate/" + id + ".png")), id); + } + } +}