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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Cooking makes food an interactive part of everyday life. Players grow and gather
- **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. It stays in the bowl 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`).
- **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, Meat, Shepherd's) and name, and its food, quality and freshness come from its dough and fillings (`pies.yml`).
- **Mushrooms and hidden poison** — grilled mushrooms go into sandwiches and salads, raw or grilled ones onto pizzas and into pies, and raw ones boil into soup; in a dish both kinds are just "Mushroom". Each red mushroom poisons the dish a step further (Tainted, Poisoned, Deadly: less and less nutrition, the same food). In a dish of 3 stars or more with its red mushrooms grilled (or its own bake cooked, its soup boiled) the poison is hidden: the lore shows no tag and the Food and Nutrition it would have without it, but eating applies it. Any dish bonus can count an origin and hide its tag this way (`count: origin`, `hide-at-stars`, `hide-tags`, `hide-dish-tags` in the dishes' `bonuses`; `tags.yml` poison).
- **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
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ public final class CookingOutput {
private CookingOutput() {}

/**
* A piece whose type has no doneness of its own (cooking-options with only outputs) starts raw once it's in the
* cookware, so the usual raw, cooked, burnt steps can follow.
* A piece whose type has no doneness of its own (cooking-options with only outputs or raw-in-cookware) starts raw
* once it's in the cookware, so the usual raw, cooked, burnt steps can follow.
*/
static void startRaw(FoodItem piece, Method method) {
if (output(piece, method) == null || piece.getCookData().cooksInPlace() || piece.hasTagTrack(COOKED_TRACK)) {
CookParameter parameter = piece.getCookData().getParameters().get(method);
if (parameter == null || !parameter.rawInCookwareOnly() || piece.getCookData().cooksInPlace()
|| piece.hasTagTrack(COOKED_TRACK)) {
return;
}
piece.addTagTrack(COOKED_TRACK);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import net.tfminecraft.cooking.quality.CompositionApplier;
import net.tfminecraft.cooking.quality.CompositionQualityResolver;
import net.tfminecraft.cooking.quality.CompositionResult;
import net.tfminecraft.cooking.sandwich.DishBonus;
import net.tfminecraft.cooking.utils.DisplayUtils;
import net.tfminecraft.cooking.utils.Encoder;
import net.tfminecraft.cooking.utils.FoodParser;
Expand Down Expand Up @@ -244,6 +245,8 @@ public void scoop(Player p, ItemStack ladle) {
CompositionResult composed = CompositionQualityResolver.compose(p, inputs, CompositionContext.SOUP_SCOOP);
CompositionApplier.apply(soup, composed);
soup.setBaseFood(scoopFood(soup.getBaseFood()));
// The pot has no layers: only bonuses that count origins (a poison) apply, over everything in it.
DishBonus.apply(soup, inputs, food -> null, layer -> false, PotConfig.bonuses());
int quality = composed.getFinalQuality();

ItemStack output = ItemBuilder.buildSingleWithQuality(soup, ladle, quality);
Expand Down
86 changes: 72 additions & 14 deletions src/main/java/net/tfminecraft/cooking/item/FoodItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,19 @@ public String getTagLabel(String trackId, String stepId) {
public int getEffectiveQuality() {
return QualityUtils.clamp(qualityMin + TagQuality.stars(tags.values()));
}

/** The stars the lore shows: as {@link #getEffectiveQuality()}, leaving out hidden tracks. */
public int getShownQuality() {
return QualityUtils.clamp(qualityMin + TagQuality.stars(shownTracks()));
}

private List<TagTrack> shownTracks() {
List<TagTrack> shown = new ArrayList<>();
for (TagTrack track : tags.values()) {
if (!track.isHidden()) shown.add(track);
}
return shown;
}
public void setQualityRange(int min, int max) {
this.qualityMin = min;
this.qualityMax = max;
Expand Down Expand Up @@ -575,16 +588,43 @@ public List<String> getCurrentTagIds() {
return current;
}

/** The ids of the tracks the lore keeps secret (TagTrack.isHidden), in lore order. */
public List<String> getHiddenTrackIds() {
List<String> hidden = new ArrayList<>();
for (TagTrack track : getTagTracks()) {
if (track.isHidden()) hidden.add(track.getId());
}
return hidden;
}

/** Hides the tracks with these ids that the food has; ids it doesn't have are ignored. */
public void hideTracks(Collection<String> trackIds) {
for (String trackId : trackIds) {
TagTrack track = tags.get(trackId);
if (track != null) track.setHidden(true);
}
}

/** True when both have the same tag steps, hidden alike, so a secret tag never stacks into a plain food. */
public boolean sameTags(FoodItem other) {
List<String> myTags = getCurrentTagIds();
List<String> otherTags = other.getCurrentTagIds();
List<String> myTags = tagKeys();
List<String> otherTags = other.tagKeys();
if(myTags.size() != otherTags.size()) return false;
for(String t : myTags) {
if(!otherTags.contains(t)) return false;
}
return true;
}

private List<String> tagKeys() {
List<String> keys = new ArrayList<>();
for (TagTrack track : tags.values()) {
TagStep step = track.getCurrentStep();
if (step != null) keys.add(step.getId() + (track.isHidden() ? "~hidden" : ""));
}
return keys;
}

public boolean hasTag(Tag tag) {
for(TagStep t : getCurrentTags()) {
if(t.getTag().equals(tag)) return true;
Expand All @@ -601,38 +641,54 @@ public boolean canBeCooked() {
// --------------------------------------------------------------
// FINAL VALUES (QUALITY + TAGS + COOK STATE)
// --------------------------------------------------------------
/** The food eating this gives: every tag counts, hidden ones too. */
public double getFinalFood() {
if (hasCarveState() && carveRemaining > 0) {
return roundToTenth(applyMultipliers(net.tfminecraft.cooking.carve.CarvableRoastUtils.getRemainingFood(this), 0)
+ sauceFinal(0));
}
return roundToTenth(applyMultipliers(baseFood, 0) + sauceFinal(0));
return finalValue(0, true);
}

/** The nutrition eating this gives: every tag counts, hidden ones too. */
public double getFinalNutrition() {
return finalValue(1, true);
}

/** The food the lore shows: as {@link #getFinalFood()}, leaving out hidden tracks. */
public double getShownFood() {
return finalValue(0, false);
}

/** The nutrition the lore shows: as {@link #getFinalNutrition()}, leaving out hidden tracks. */
public double getShownNutrition() {
return finalValue(1, false);
}

private double finalValue(int type, boolean withHidden) {
double base;
if (hasCarveState() && carveRemaining > 0) {
return roundToTenth(applyMultipliers(net.tfminecraft.cooking.carve.CarvableRoastUtils.getRemainingNutrition(this), 1)
+ sauceFinal(1));
base = type == 1 ? net.tfminecraft.cooking.carve.CarvableRoastUtils.getRemainingNutrition(this)
: net.tfminecraft.cooking.carve.CarvableRoastUtils.getRemainingFood(this);
} else {
base = type == 1 ? baseNutrition : baseFood;
}
return roundToTenth(applyMultipliers(baseNutrition, 1) + sauceFinal(1));
return roundToTenth(applyMultipliers(base, type, withHidden) + sauceFinal(type, withHidden));
}

private double sauceFinal(int type) {
private double sauceFinal(int type, boolean withHidden) {
if (!hasSauce()) {
return 0;
}
return type == 1 ? getSauce().getFinalNutrition() : getSauce().getFinalFood();
return getSauce().finalValue(type, withHidden);
}

private double applyMultipliers(double base, int type) {
int stars = getEffectiveQuality();
private double applyMultipliers(double base, int type, boolean withHidden) {
int stars = withHidden ? getEffectiveQuality() : getShownQuality();
double qualityMultiplier = type == 1
? net.tfminecraft.cooking.quality.QualityConfig.nutritionMultiplier(stars)
: 1.0 + (stars - 1) * 0.20;

double result = base * qualityMultiplier;

for (TagTrack t : tags.values()) {
if (!withHidden && t.isHidden()) continue;
TagStep step = t.getCurrentStep();
if (step != null) {
if (type == 1) result *= step.getNutritionMultiplier();
Expand Down Expand Up @@ -713,6 +769,8 @@ public static FoodItem fromItem(ItemStack stack) {
out.addOrModifyTrack(t);
}
}
String hiddenData = pdc.get(Keys.HIDDEN_TAGS, PersistentDataType.STRING);
if (hiddenData != null) out.hideTracks(Arrays.asList(hiddenData.split(":")));

out.decodeAgeRemainder(pdc.get(Keys.AGE_REMAINDER, PersistentDataType.STRING));

Expand Down
7 changes: 4 additions & 3 deletions src/main/java/net/tfminecraft/cooking/item/data/CookData.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public CookData(String id, ConfigurationSection config) {
ConfigurationSection param = config.getConfigurationSection(key);
try {
parameters.put(Method.valueOf(key.toUpperCase(java.util.Locale.ROOT)), new CookParameter(param.getInt("tag", 1), param.getInt("time", 15), param.getInt("burn", 30),
param.getString("output", null)));
param.getString("output", null), param.getBoolean("raw-in-cookware", false)));
} catch (Exception e) {
Bukkit.getLogger().info("Failed to create cooking data for "+id);
}
Expand Down Expand Up @@ -56,11 +56,12 @@ public boolean hasMethod(Method m) {

/**
* True when some method cooks this food as itself, so it carries a cooked track (Raw) from the start. A food whose
* only options name an output (chopped corn, which pops into popcorn) is not raw until it's in the cookware.
* only options name an output (chopped corn, which pops into popcorn) or are raw-in-cookware (a mushroom, which
* boils in the pot) is not raw until it's in the cookware.
*/
public boolean cooksInPlace() {
for (CookParameter parameter : parameters.values()) {
if (parameter.getOutput() == null) {
if (!parameter.rawInCookwareOnly()) {
return true;
}
}
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/net/tfminecraft/cooking/item/data/CookParameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,30 @@
/**
* One cooking option of a food type (types.yml cooking-options.&lt;method&gt;): the cooked tag it reaches, when, when
* it burns, and optionally the food it turns into once it's done (output), such as chopped corn popping into popcorn.
* An option with an output, or with raw-in-cookware: true (a mushroom boiling in the pot), doesn't make the food Raw
* in hand: it starts raw once it's in that cookware.
*/
public class CookParameter {
private int tag;
private int time;
private int burntime;
private final String output;
private final boolean rawInCookware;

public CookParameter(int tag, int time, int burntime) {
this(tag, time, burntime, null);
}

public CookParameter(int tag, int time, int burntime, String output) {
this(tag, time, burntime, output, false);
}

public CookParameter(int tag, int time, int burntime, String output, boolean rawInCookware) {
this.tag = tag;
this.time = time;
this.burntime = burntime;
this.output = output == null || output.isBlank() ? null : output.trim();
this.rawInCookware = rawInCookware;
}

public int getTag() {
Expand All @@ -36,6 +44,11 @@ public String getOutput() {
return output;
}

/** True when the food is only Raw once it's in this cookware: the option has an output or raw-in-cookware. */
public boolean rawInCookwareOnly() {
return output != null || rawInCookware;
}

/** The same timings without an output: what the piece keeps cooking by once it has turned into its output. */
public CookParameter withoutOutput() {
return output == null ? this : new CookParameter(tag, time, burntime);
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/net/tfminecraft/cooking/item/tag/TagTrack.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;

/**
* One tags.yml track on a food: its steps and the value that picks the current one. A hidden track (a poison a cook
* slipped in) still applies its multipliers when the food is eaten, but the lore leaves its line out and shows Food
* and Nutrition without it (FoodItem getShownFood / getShownNutrition); the item keeps it hidden through rebuilds.
*/
public class TagTrack {
private String id;
private boolean ageable;
private int value;
private int index;
private boolean hidden;
private final List<TagStep> steps = new ArrayList<>();

public TagTrack(String key, int i, ConfigurationSection config) {
Expand All @@ -34,6 +40,7 @@ public TagTrack(TagTrack other) {
this.value = other.value;
this.ageable = other.ageable;
this.index = other.index;
this.hidden = other.hidden;
this.steps.addAll(other.steps);
}

Expand Down Expand Up @@ -64,6 +71,10 @@ public void forceSetValue(int value) {
}
public boolean isAgeable() { return ageable; }

/** True when the lore keeps this track secret: its line and its effect on the shown Food and Nutrition. */
public boolean isHidden() { return hidden; }
public void setHidden(boolean hidden) { this.hidden = hidden; }

public TagStep getCurrentStep() {
if (steps.isEmpty()) return null;
TagStep current = null;
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/net/tfminecraft/cooking/pie/PieItems.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,15 @@ public static ItemStack build(Player player, ItemStack dough, List<ItemStack> fi
CompositionApplier.apply(pie, composed);
CompositionFreshnessApplier.keepOwnAgeing(pie);
SandwichItems.applyTotals(pie, all, true);
DishBonus.apply(pie, all, PieConfig::layerFor, layer -> !PieConfig.isCrust(layer), PieConfig.bonuses());
name(pie, filled);
TagTrack cooked = TrackLoader.getByString(COOKED_TRACK);
if (cooked != null) {
TagTrack bake = new TagTrack(cooked);
bake.setValue(baked);
pie.addOrModifyTrack(bake);
}
// After the bake: a bonus hidden in a well-made pie (a poison) needs it cooked, not burnt.
DishBonus.apply(pie, all, PieConfig::layerFor, layer -> !PieConfig.isCrust(layer), PieConfig.bonuses());
name(pie, filled);
WarmthUtils.applyHot(pie);
return ItemBuilder.buildComposedWithQuality(pie, composed.getFinalQuality());
}
Expand Down
13 changes: 7 additions & 6 deletions src/main/java/net/tfminecraft/cooking/pizza/PizzaItems.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ public static ItemStack build(Player player, List<ItemStack> stacks, int baked)
CompositionApplier.apply(pizza, composed);
CompositionFreshnessApplier.keepOwnAgeing(pizza);
applyTotals(pizza, layers);
TagTrack cooked = TrackLoader.getByString(COOKED_TRACK);
if (cooked != null) {
TagTrack bake = new TagTrack(cooked);
bake.setValue(baked);
pizza.addOrModifyTrack(bake);
}
// After the bake: a bonus hidden in a well-made pizza (a poison) needs it cooked, not burnt.
DishBonus.apply(pizza, layers, PizzaConfig::layerFor,
layer -> !PizzaConfig.isBase(layer) && !PizzaConfig.isSauce(layer), PizzaConfig.bonuses());
for (FoodItem food : layers) {
Expand All @@ -56,12 +63,6 @@ public static ItemStack build(Player player, List<ItemStack> stacks, int baked)
String name = layer.fillerName(food);
if (!name.isEmpty()) pizza.addIngredient(name);
}
TagTrack cooked = TrackLoader.getByString(COOKED_TRACK);
if (cooked != null) {
TagTrack bake = new TagTrack(cooked);
bake.setValue(baked);
pizza.addOrModifyTrack(bake);
}
return ItemBuilder.buildComposedWithQuality(pizza, composed.getFinalQuality());
}

Expand Down
13 changes: 12 additions & 1 deletion src/main/java/net/tfminecraft/cooking/pot/PotConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@
import org.bukkit.configuration.file.YamlConfiguration;

import net.tfminecraft.cooking.item.FoodItem;
import net.tfminecraft.cooking.sandwich.DishBonus;

/** pot.yml: what the soup pot takes besides a water bucket (stock), and the dishes it mashes without water. */
/**
* pot.yml: what the soup pot takes besides a water bucket (stock), the dishes it mashes without water, and the tags a
* soup earns from what's in the pot (bonuses, such as a poison).
*/
public final class PotConfig {
private static List<PotLiquid> liquids = List.of();
private static List<MashRecipe> mashes = List.of();
private static List<DishBonus> bonuses = List.of();

public void load(File file) {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
Expand All @@ -34,6 +39,12 @@ public void load(File file) {
}
liquids = poured;
mashes = recipes;
bonuses = DishBonus.load(config.getConfigurationSection("bonuses"));
}

/** The tags a ladled soup earns from what's in the pot, written like sandwiches.yml's bonuses (count: origin). */
public static List<DishBonus> bonuses() {
return bonuses;
}

/** The liquid this food pours into the pot, or null. */
Expand Down
Loading
Loading