Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Cooking makes food an interactive part of everyday life. Players grow and gather

- **Working kitchens** — fry, boil, prepare sauces and soups, and bake with cookware, heat sources, and fuelled ovens. Leaving food cooking too long can burn it.
- **Food preparation** — milling stones, mixing bowls, butter churns, sausage makers, and baking trays provide different ways to transform ingredients.
- **Sandwiches** — lay a slice of bread on a cutting board, add spreads, cheese and vegetables, close it with another slice and pick it up. Its fillings name it, and its food, quality and freshness come from its layers (`sandwiches.yml`).
- **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`).
- **Sandwiches** — lay a slice of bread on a cutting board, add spreads, cheese and vegetables, close it with another slice and pick it up. Its fillings name it, and its food, quality and freshness come from its layers (`sandwiches.yml`). Its layers also earn it bonuses such as Hearty, Buttered or Well-Rounded (`bonuses` in `sandwiches.yml`).
- **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`).
- **Serving and sharing** — plate dishes, ladle soup into bowls, and carve foods into portions for the table.
- **Quality and freshness** — ingredient quality and composition influence finished dishes, while aging and food tags carry preparation history through the kitchen.
- **Farming and husbandry** — crop fertility and harvest quality sit alongside animal care, breeding, genetics, and produce collection.
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/net/tfminecraft/cooking/pizza/PizzaConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.bukkit.configuration.file.YamlConfiguration;

import net.tfminecraft.cooking.item.FoodItem;
import net.tfminecraft.cooking.sandwich.DishBonus;
import net.tfminecraft.cooking.sandwich.SandwichLayer;
import net.tfminecraft.interactiblefurniture.furniture.Furniture;

Expand All @@ -27,6 +28,7 @@ public final class PizzaConfig {
private static SandwichLayer base;
private static SandwichLayer sauce;
private static List<SandwichLayer> toppings = List.of();
private static List<DishBonus> bonuses = List.of();

public void load(File file) {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
Expand All @@ -49,6 +51,7 @@ public void load(File file) {
base = layer(config, "base");
sauce = layer(config, "sauce");
toppings = loaded;
bonuses = DishBonus.load(config.getConfigurationSection("bonuses"));
}

private static SandwichLayer layer(YamlConfiguration config, String id) {
Expand Down Expand Up @@ -80,6 +83,11 @@ public static boolean isSauce(SandwichLayer layer) {
return layer != null && layer == sauce;
}

/** The tags a pizza earns from its layers (Saucy, Loaded...). */
public static List<DishBonus> bonuses() {
return bonuses;
}

public static int maxToppings() {
return maxToppings;
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/net/tfminecraft/cooking/pizza/PizzaItems.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import net.tfminecraft.cooking.quality.CompositionContext;
import net.tfminecraft.cooking.quality.CompositionQualityResolver;
import net.tfminecraft.cooking.quality.CompositionResult;
import net.tfminecraft.cooking.sandwich.DishBonus;
import net.tfminecraft.cooking.sandwich.SandwichLayer;
import net.tfminecraft.cooking.utils.FoodParser;
import net.tfminecraft.cooking.utils.ItemBuilder;
Expand Down Expand Up @@ -44,6 +45,8 @@ public static ItemStack build(Player player, List<ItemStack> stacks, int baked)
CompositionResult composed = CompositionQualityResolver.compose(player, layers, CompositionContext.PIZZA);
CompositionApplier.apply(pizza, composed);
applyTotals(pizza, layers);
DishBonus.apply(pizza, layers, PizzaConfig::layerFor,
layer -> !PizzaConfig.isBase(layer) && !PizzaConfig.isSauce(layer), PizzaConfig.bonuses());
for (FoodItem food : layers) {
SandwichLayer layer = PizzaConfig.layerFor(food);
if (layer == null || PizzaConfig.isBase(layer) || PizzaConfig.isSauce(layer)) continue;
Expand Down
140 changes: 140 additions & 0 deletions src/main/java/net/tfminecraft/cooking/sandwich/DishBonus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package net.tfminecraft.cooking.sandwich;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.function.Predicate;

import org.bukkit.configuration.ConfigurationSection;

import net.tfminecraft.cooking.item.FoodItem;
import net.tfminecraft.cooking.item.tag.TagStep;
import net.tfminecraft.cooking.item.tag.TagTrack;
import net.tfminecraft.cooking.loader.TrackLoader;
import net.tfminecraft.cooking.utils.StationAddonRules;

/**
* A tag a finished sandwich or pizza earns from its layers (sandwiches.yml / pizzas.yml bonuses), like a soup's
* Seasoned or Thick: a step of a tags.yml track, whose multipliers then apply to the dish.
*
* <pre>
* bonuses:
* loaded:
* track: dish_size
* count: fillings # fillings (or toppings), or seasonings: different salts and peppers
* steps: {3: hearty, 5: loaded}
* well_rounded:
* track: well_rounded
* layers: [cheese, vegetable] # every one of these layers
* any-of: [jam, butter] # and at least one of these
* min: 2 # each of the layers above at least this often
* addons: rounded # a chopped garnish and a spice (aromatic: two of one kind)
* valuable: true # a food types.yml marks valuable (tomato, onion...)
* </pre>
* A bonus with no count takes its track's first step, or the one named in step.
*/
public final class DishBonus {
private final String track;
private final String step;
private final List<String> layers = new ArrayList<>();
private final List<String> anyOf = new ArrayList<>();
private final int min;
private final String addons;
private final boolean valuable;
private final String count;
private final TreeMap<Integer, String> steps = new TreeMap<>();

public DishBonus(ConfigurationSection config) {
this.track = config.getString("track", config.getName());
this.step = config.getString("step");
for (String id : config.getStringList("layers")) layers.add(id.toLowerCase(Locale.ROOT));
for (String id : config.getStringList("any-of")) anyOf.add(id.toLowerCase(Locale.ROOT));
this.min = Math.max(1, config.getInt("min", 1));
String profile = config.getString("addons");
this.addons = profile == null ? null : profile.toUpperCase(Locale.ROOT);
this.valuable = config.getBoolean("valuable", false);
this.count = config.getString("count");
ConfigurationSection counted = config.getConfigurationSection("steps");
if (counted != null) {
for (String at : counted.getKeys(false)) {
try {
steps.put(Integer.parseInt(at.trim()), counted.getString(at));
} catch (NumberFormatException ignored) {
// Not a count: skip it.
}
}
}
}

/** The bonuses in a sandwiches.yml or pizzas.yml bonuses section; none when it's missing. */
public static List<DishBonus> load(ConfigurationSection section) {
List<DishBonus> loaded = new ArrayList<>();
if (section == null) return loaded;
for (String id : section.getKeys(false)) {
ConfigurationSection bonus = section.getConfigurationSection(id);
if (bonus != null) loaded.add(new DishBonus(bonus));
}
return loaded;
}

/**
* Gives the dish every bonus its layers earn. The layer function says which layer a food makes (null for none);
* the filling test picks the layers that count as fillings or toppings (not bread, dough or sauce).
*/
public static void apply(FoodItem dish, List<FoodItem> foods, Function<FoodItem, SandwichLayer> layerFor,
Predicate<SandwichLayer> filling, List<DishBonus> bonuses) {
Map<String, Integer> counts = new HashMap<>();
Map<String, FoodItem> addonsByOrigin = new HashMap<>();
Set<String> seasonings = new HashSet<>();
Map<String, FoodItem> all = new HashMap<>();
int fillings = 0;
for (FoodItem food : foods) {
SandwichLayer layer = layerFor.apply(food);
if (layer == null) continue;
all.put(String.valueOf(all.size()), food);
counts.merge(layer.getId().toLowerCase(Locale.ROOT), 1, Integer::sum);
if (filling.test(layer)) fillings++;
String origin = food.getOrigin() == null ? "" : food.getOrigin().toLowerCase(Locale.ROOT);
if (StationAddonRules.isAddonCategory(food.getCategory())) addonsByOrigin.putIfAbsent(origin, food);
if (StationAddonRules.isSeasoningCategory(food.getCategory())) seasonings.add(origin);
}
String profile = StationAddonRules.classifyAddons(addonsByOrigin).name();
boolean hasValuable = StationAddonRules.hasValuable(all);
for (DishBonus bonus : bonuses) {
int counted = "seasonings".equalsIgnoreCase(bonus.count) ? seasonings.size() : fillings;
String earned = bonus.earned(counts, counted, profile, hasValuable);
if (earned != null) give(dish, bonus.track, earned);
}
}

/** The step this bonus gives, or null when the layers don't earn it ("" means the track's first step). */
String earned(Map<String, Integer> counts, int counted, String profile, boolean hasValuable) {
for (String layer : layers) {
if (counts.getOrDefault(layer, 0) < min) return null;
}
if (!anyOf.isEmpty() && anyOf.stream().noneMatch(counts::containsKey)) return null;
if (addons != null && !addons.equals(profile)) return null;
if (valuable && !hasValuable) return null;
if (count == null) return step == null ? "" : step;
Map.Entry<Integer, String> reached = steps.floorEntry(counted);
return reached == null ? null : reached.getValue();
}

private static void give(FoodItem dish, String trackId, String stepId) {
TagTrack template = TrackLoader.getByString(trackId);
if (template == null || template.getSteps().isEmpty()) return;
TagTrack track = new TagTrack(template);
int value = (int) track.getSteps().get(0).getRequiredValue();
for (TagStep step : track.getSteps()) {
if (step.getId().equalsIgnoreCase(stepId)) value = (int) step.getRequiredValue();
}
track.forceSetValue(value);
dish.addOrModifyTrack(track);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public final class SandwichConfig {
private static int maxFillings = 6;
private static String output = "meal(type=sandwich;origin=Mixed)";
private static List<SandwichLayer> layers = List.of();
private static List<DishBonus> bonuses = List.of();

public void load(File file) {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
Expand All @@ -36,6 +37,7 @@ public void load(File file) {
maxFillings = Math.max(1, config.getInt("max-fillings", 6));
output = config.getString("output", "meal(type=sandwich;origin=Mixed)");
layers = loaded;
bonuses = DishBonus.load(config.getConfigurationSection("bonuses"));
}

public static boolean isSandwichFurniture(Furniture f) {
Expand All @@ -54,6 +56,11 @@ public static int maxFillings() {
return maxFillings;
}

/** The tags a sandwich earns from its layers (Hearty, Buttered...). */
public static List<DishBonus> bonuses() {
return bonuses;
}

/** The finished sandwich's food line (types.yml type, origin, tags). */
public static String output() {
return output;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public static ItemStack build(Player player, List<ItemStack> stacks) {
CompositionResult composed = CompositionQualityResolver.compose(player, layers, CompositionContext.SANDWICH);
CompositionApplier.apply(sandwich, composed);
applyTotals(sandwich, layers);
DishBonus.apply(sandwich, layers, SandwichConfig::layerFor, layer -> !layer.isBread(), SandwichConfig.bonuses());
boolean marked = false;
for (FoodItem food : layers) {
SandwichLayer layer = SandwichConfig.layerFor(food);
Expand Down
32 changes: 32 additions & 0 deletions src/main/resources/pizzas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,38 @@ bake:
cook-seconds: 20
burn-seconds: 60

# Tags a pizza earns from its layers, written like sandwiches.yml's bonuses. count: fillings counts the toppings;
# count: seasonings counts different salts and peppers among them.
bonuses:
size:
track: dish_size
count: fillings
steps:
3: hearty
5: loaded
saucy:
track: saucy
layers: [sauce]
extra_cheesy:
track: extra_cheesy
layers: [cheese]
min: 2
seasoning:
track: seasoning
count: seasonings
steps:
1: seasoned
2: well_seasoned
rounded:
track: rounded
addons: rounded
aromatic:
track: aromatic
addons: aromatic
sweet_and_savoury:
track: sweet_and_savoury
layers: [fruit, sausage]

# Layers, matched by types.yml type, written like sandwiches.yml's:
# name the topping's part of the pizza's name; {origin} is the food's origin (Pineapple)
# names a name per origin instead (goat: Goat Cheese)
Expand Down
25 changes: 25 additions & 0 deletions src/main/resources/sandwiches.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,31 @@ max-fillings: 6
# The finished sandwich (types.yml). Food and nutrition are replaced by its layers'.
output: meal(type=sandwich;origin=Mixed)

# Tags a sandwich earns from its layers, like a soup's Seasoned or Thick (tags.yml tracks; their multipliers apply).
# track the tags.yml track; step picks one of its steps (default: its first)
# count fillings: the number of fillings, with steps giving the step reached at each count
# layers every one of these layers (ids below); min: each at least this often
# any-of at least one of these layers
# valuable a food types.yml marks valuable (tomato, onion, lettuce, cucumber, corn)
# addons rounded: a chopped garnish and a spice; aromatic: two of one kind
bonuses:
size:
track: dish_size
count: fillings
steps:
3: hearty
5: loaded
buttered:
track: buttered
layers: [butter]
well_rounded:
track: well_rounded
layers: [cheese, vegetable]
any-of: [jam, paste, catsup, mustard, butter]
flavourful:
track: flavourful
valuable: true

# What can go in a sandwich, matched by types.yml type; the first layer that takes a food wins.
# bread: true a slice that starts and closes the sandwich (it doesn't name it)
# name the filling's part of the sandwich's name; {origin} is the food's origin (Strawberry)
Expand Down
46 changes: 46 additions & 0 deletions src/main/resources/tags.yml
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,49 @@ reduced:
value: 0
nutrition-mult: 1.10
food-mult: 1.10

# Sandwich and pizza bonuses (sandwiches.yml / pizzas.yml bonuses).
dish_size:
hearty:
name: "#c08a4bHearty"
value: 0
food-mult: 1.10
loaded:
name: "#a0522dLoaded"
value: 1
food-mult: 1.20
nutrition-mult: 1.05

buttered:
buttered:
name: "#f3e08aButtered"
value: 0
food-mult: 1.05
nutrition-mult: 1.10

well_rounded:
well_rounded:
name: "#7cb342Well-Rounded"
value: 0
food-mult: 1.15
nutrition-mult: 1.15

saucy:
saucy:
name: "#c0392bSaucy"
value: 0
food-mult: 1.10

extra_cheesy:
extra_cheesy:
name: "#f1c232Extra Cheesy"
value: 0
food-mult: 1.15
nutrition-mult: 1.05

sweet_and_savoury:
sweet_and_savoury:
name: "#e07b53Sweet and Savoury"
value: 0
food-mult: 1.10
nutrition-mult: 1.10
Loading
Loading