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 .github/dependencies.sha256
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
2cbd119bf1961c28788310963dc80ba65f58cdeec1dd139c8bdb1240faa2c36f libs/gson-2.14.0.jar
feca4db853371704338621c120acc5cc32300d8af635fa42414b6213301e216b libs/joml-1.10.9.jar
225aa7f75d4e6e6ae6b3ad87a7ca678c025c027f00374ab1062275d24f3a73c9 libs/MythicLib-1.7.1-SNAPSHOT.jar
a37f7789fcdcd11c9e5890a2fac742aa4501749588af23182991953e06ee1d17 libs/MMOItems-6.10.1-SNAPSHOT.jar
81d511d0830951f988615b6533cc93e82d00a83fb31e8ccbb36441ae3e8157ef libs/MMOCore-1.13.1-SNAPSHOT.jar
93317bf08c9dae4a645644caa313b5436b19e65cc13abf9f3e0afb951bd6c1d2 libs/CustomCrops-3.6.56.jar
7d3083bd05998d8cafd7114ced74a6fa2ec1c4d36f75dbbaee9f20d499a7c3ad libs/CustomFishing-2.3.27.jar
3 changes: 3 additions & 0 deletions .github/scripts/install-local-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ mvn -B --no-transfer-progress org.apache.maven.plugins:maven-install-plugin:3.1.
mvn -B --no-transfer-progress org.apache.maven.plugins:maven-install-plugin:3.1.4:install-file \
-Dfile="libs/MythicLib-1.7.1-SNAPSHOT.jar" -DgroupId="local" -DartifactId="MythicLib" \
-Dversion="1.7.1-SNAPSHOT-tfmc-225aa7f75d4e" -Dpackaging=jar -DgeneratePom=true "$@"
mvn -B --no-transfer-progress org.apache.maven.plugins:maven-install-plugin:3.1.4:install-file \
-Dfile="libs/MMOItems-6.10.1-SNAPSHOT.jar" -DgroupId="local" -DartifactId="MMOItems" \
-Dversion="6.10.1-SNAPSHOT-tfmc-a37f7789fcdc" -Dpackaging=jar -DgeneratePom=true "$@"
mvn -B --no-transfer-progress org.apache.maven.plugins:maven-install-plugin:3.1.4:install-file \
-Dfile="libs/MMOCore-1.13.1-SNAPSHOT.jar" -DgroupId="local" -DartifactId="MMOCore" \
-Dversion="1.13.1-SNAPSHOT-tfmc-81d511d08309" -Dpackaging=jar -DgeneratePom=true "$@"
Expand Down
1 change: 1 addition & 0 deletions .github/scripts/prepare-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mkdir -p libs
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/2cbd119bf196/gson-2.14.0.jar?ref=$ref" > "libs/gson-2.14.0.jar"
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/feca4db85337/joml-1.10.9.jar?ref=$ref" > "libs/joml-1.10.9.jar"
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/225aa7f75d4e/MythicLib-1.7.1-SNAPSHOT.jar?ref=$ref" > "libs/MythicLib-1.7.1-SNAPSHOT.jar"
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/a37f7789fcdc/MMOItems-6.10.1-SNAPSHOT.jar?ref=$ref" > "libs/MMOItems-6.10.1-SNAPSHOT.jar"
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/81d511d08309/MMOCore-1.13.1-SNAPSHOT.jar?ref=$ref" > "libs/MMOCore-1.13.1-SNAPSHOT.jar"
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/93317bf08c9d/CustomCrops-3.6.56.jar?ref=$ref" > "libs/CustomCrops-3.6.56.jar"
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/7d3083bd0599/CustomFishing-2.3.27.jar?ref=$ref" > "libs/CustomFishing-2.3.27.jar"
Expand Down
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
- **Farming and husbandry** — crop fertility and harvest quality sit alongside animal care, breeding, genetics, and produce collection.
- **Fish and seafood** — turn supported catches into cooking ingredients while preserving catch size.
- **Character nutrition** — meals feed a character's food reserve and influence diet quality, with variety rewarding a broader selection of ingredients.
- **MMOItems crafting stations** — the `cooking{item="seafood(type=seafood_whole;origin=Salmon)",amount=64,display="Salmon"}` ingredient takes Cooking food, plus the plain items Cooking converts on pickup (a fresh catch counts as the salmon it becomes). `item` is a `c.` path without the prefix and must name a food `type`; quality and tags filters work too. Cooking claims those items before MMOItems' `vanilla{}` type, so use `cooking{}` for salmon, wheat, sugar, beef and the other converted foods. Cancelling a queued craft refunds the least the line accepts: its lowest quality, aged out unless the line pins that tag.

## A complete food journey

Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@
<version>1.7.1-SNAPSHOT-tfmc-225aa7f75d4e</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>local</groupId>
<artifactId>MMOItems</artifactId>
<version>6.10.1-SNAPSHOT-tfmc-a37f7789fcdc</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>local</groupId>
<artifactId>MMOCore</artifactId>
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/net/tfminecraft/cooking/Cooking.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
import net.tfminecraft.cooking.milling.MillingStoneHandler;
import net.tfminecraft.cooking.mixing.MixingBowlHandler;
import net.tfminecraft.cooking.heat.HeatPickupGuard;
import net.tfminecraft.cooking.mmoitems.MMOItemsSupport;
import net.tfminecraft.cooking.nutrition.BowlEatHandler;
import net.tfminecraft.cooking.nutrition.FoodConsumeListener;
import net.tfminecraft.cooking.nutrition.FoodLevelChangeGuard;
Expand Down Expand Up @@ -124,6 +125,11 @@ public class Cooking extends JavaPlugin {
private final OvenHandler ovenHandler = new OvenHandler(ovenBurnManager);
private final CraftingManager craftingManager = new CraftingManager();

@Override
public void onLoad() {
MMOItemsSupport.registerIfPresent(getServer().getPluginManager(), getLogger());
}

@Override
public void onEnable() {
plugin = this;
Expand Down Expand Up @@ -154,6 +160,7 @@ public void onEnable() {
HusbandryLifecycleListener.resumeLoadedWorlds();
HusbandryTickTask.start();
HusbandryLocator.scanUnloaded();
MMOItemsSupport.claimFirstIfPresent(getServer().getPluginManager(), getLogger());
});

getCommand("cooking").setExecutor(commands);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public boolean matches(ItemStack item, String fullPath) {
return matches(FoodItem.fromItem(item), fullPath);
}

static boolean matches(FoodItem food, String fullPath) {
public static boolean matches(FoodItem food, String fullPath) {
if (food == null || fullPath == null) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package net.tfminecraft.cooking.mmoitems;

import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;

import io.lumine.mythic.lib.api.MMOLineConfig;
import net.Indyuce.mmoitems.api.crafting.ingredient.Ingredient;
import net.Indyuce.mmoitems.api.player.RPGPlayer;
import net.tfminecraft.cooking.item.CookingPathHandler;
import net.tfminecraft.cooking.item.FoodItem;
import net.tfminecraft.cooking.item.tag.AgeScale;
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.FoodParser;
import net.tfminecraft.cooking.utils.ItemBuilder;

/**
* {@code cooking{item="seafood(type=seafood_whole;origin=Salmon)",amount=64,display="Salmon"}}.
* {@code item} is a Cooking path without the {@code c.} prefix, matched like every other
* {@code c.} path. It must name a food type: MMOItems hands that food back when a queued craft
* is cancelled.
*/
public class CookingStationIngredient extends Ingredient<CookingStationPlayerIngredient> {
private final String path;
private final Map<String, String> fields;
private final String display;

public CookingStationIngredient(MMOLineConfig config) {
super(MMOItemsIngredients.ID, config);
config.validate("item");
path = CookingPathHandler.stripPrefix(config.getString("item"));
int paren = path.indexOf('(');
fields = paren < 0 || !path.endsWith(")") ? Map.of()
: FoodParser.extractFields(path.substring(paren + 1, path.length() - 1));
String type = field("type");
if (type == null) {
throw new IllegalArgumentException("cooking ingredient item=\"" + path
+ "\" needs a food type, e.g. seafood(type=seafood_whole;origin=Salmon)");
}
String origin = field("origin");
display = config.getString("display", origin != null ? origin : words(type));
}

private String field(String key) {
for (Map.Entry<String, String> entry : fields.entrySet()) {
if (entry.getKey().trim().equalsIgnoreCase(key) && !entry.getValue().isBlank()) {
return entry.getValue().trim();
}
}
return null;
}

private static String words(String id) {
StringBuilder out = new StringBuilder();
for (String word : id.split("_+")) {
if (word.isEmpty()) {
continue;
}
if (out.length() > 0) {
out.append(' ');
}
out.append(word.substring(0, 1).toUpperCase(Locale.ROOT)).append(word.substring(1));
}
return out.toString();
}

public String getPath() {
return path;
}

public String getDisplayName() {
return display;
}

@Override
public String formatDisplay(String format) {
return format.replace("#item#", display).replace("#amount#", String.valueOf(getAmount()));
}

@Override
public boolean matches(CookingStationPlayerIngredient ingredient) {
return CookingPathHandler.matches(ingredient.getFood(), path);
}

/**
* The food is what its plain item becomes on pickup (a whole salmon, wheat with its freshness
* track), else the food the path names, with the tag values the line pins. The preview shows
* it fresh. A refund (forDisplay false) cannot know what was spent, so it is the least the
* line accepts: its lowest quality, aged out on every track the line leaves open. Cancelling
* never improves food.
*/
@Override
public ItemStack generateItemStack(RPGPlayer player, boolean forDisplay) {
FoodParser.Result parsed = FoodParser.parse(path);
ItemStack source = StationFood.plainSource(path);
FoodItem food = source != null ? StationFood.describe(source) : parsed == null ? null : parsed.template;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
ItemStack stack = null;
if (food != null) {
Map<String, Integer> pins = pinnedTracks();
applyPins(food, pins);
if (!forDisplay) {
ageOpenTracks(food, pins.keySet());
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
boolean explicit = parsed != null && parsed.explicitQuality;
stack = ItemBuilder.buildSingleWithQuality(food, source,
explicit ? parsed.template._parsedQualMin : StationFood.PLAIN_QUALITY);
}
if (stack == null) {
// Cooking does not know this food type (yet).
stack = placeholder();
}
stack.setAmount(forDisplay ? Math.max(1, Math.min(getAmount(), stack.getMaxStackSize())) : getAmount());
return stack;
}

private static void applyPins(FoodItem food, Map<String, Integer> pins) {
for (Map.Entry<String, Integer> pin : pins.entrySet()) {
TagTrack track = track(food, pin.getKey());
if (track != null) {
track.forceSetValue(pin.getValue());
continue;
}
TagTrack base = TrackLoader.getByString(pin.getKey());
if (base != null) {
TagTrack added = new TagTrack(base);
added.forceSetValue(pin.getValue());
food.addOrModifyTrack(added);
}
}
}

private static TagTrack track(FoodItem food, String id) {
for (TagTrack track : food.getTagTracks()) {
if (track.getId().equalsIgnoreCase(id)) {
return track;
}
}
return null;
}

private static void ageOpenTracks(FoodItem food, Set<String> pinned) {
for (TagTrack track : food.getTagTracks()) {
List<TagStep> steps = track.getSteps();
if (track.isAgeable() && !steps.isEmpty() && !pinned.contains(track.getId().toLowerCase(Locale.ROOT))) {
track.forceSetValue((int) steps.get(steps.size() - 1).getRequiredValue());
}
}
}

/**
* Track id to the value the line requires. Matching ignores a tag without a usable value, so
* such a tag constrains nothing and its track still ages out on a refund.
*/
private Map<String, Integer> pinnedTracks() {
Map<String, Integer> pinned = new HashMap<>();
String tags = field("tags");
if (tags != null) {
for (String tag : tags.split("[,:]")) {
String[] parts = tag.split("\\.", 2);
if (parts.length < 2) {
continue;
}
String raw = parts[0].trim();
try {
int value = AgeScale.migrateTrackValue(raw, Integer.parseInt(parts[1].trim()));
pinned.put(AgeScale.migrateTrackId(raw).toLowerCase(Locale.ROOT), value);
} catch (NumberFormatException e) {
// Not a number: the line accepts nothing for this tag, so there is no value to keep.
}
}
}
return pinned;
}

@SuppressWarnings("deprecation")
private ItemStack placeholder() {
ItemStack stack = new ItemStack(Material.PAPER);
ItemMeta meta = stack.getItemMeta();
meta.setDisplayName(display);
stack.setItemMeta(meta);
return stack;
}

@Override
public String toString() {
return "cooking{item=\"" + path + "\",amount=" + getAmount() + "}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package net.tfminecraft.cooking.mmoitems;

import io.lumine.mythic.lib.api.item.NBTItem;
import net.Indyuce.mmoitems.api.crafting.ingredient.inventory.PlayerIngredient;
import net.tfminecraft.cooking.item.FoodItem;

/** An inventory stack MMOItems sorted into the cooking ingredient type, with the food it counts as. */
public class CookingStationPlayerIngredient extends PlayerIngredient {
private final FoodItem food;

public CookingStationPlayerIngredient(NBTItem item) {
super(item);
food = StationFood.describe(item.getItem());
}

public FoodItem getFood() {
return food;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package net.tfminecraft.cooking.mmoitems;

import java.util.List;
import java.util.logging.Logger;

import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.crafting.ConditionalDisplay;
import net.Indyuce.mmoitems.api.crafting.ingredient.IngredientType;
import net.Indyuce.mmoitems.manager.CraftingManager;

/**
* Registers the {@code cooking} crafting station ingredient. MMOItems checks newly registered
* types first, so Cooking food and the plain items Cooking converts are claimed here before the
* {@code vanilla} type, which only accepts unnamed items. Use {@code cooking{...}} for them.
*/
public final class MMOItemsIngredients {
public static final String ID = "cooking";

private MMOItemsIngredients() {}

static void register(Logger logger) {
register(MMOItems.plugin.getCrafting(), logger);
}

static void register(CraftingManager crafting, Logger logger) {
crafting.registerIngredient(ID, CookingStationIngredient::new,
new ConditionalDisplay("&a✔ &7#amount# #item#", "&c✖ &7#amount# #item#"),
nbt -> StationFood.describe(nbt.getItem()) != null,
CookingStationPlayerIngredient::new);
logger.info("Registered the MMOItems crafting station ingredient cooking{item=...}.");
}

static void claimFirst(Logger logger) {
claimFirst(MMOItems.plugin.getCrafting(), logger);
}

/**
* MMOItems registers its ItemsAdder (and Nexo, Oraxen, MythicMobs) types while it enables,
* after onLoad, and each goes in front. Cooking food built on an ItemsAdder model would then
* be read as an ItemsAdder item, so put cooking back in front once everything has enabled.
*/
static void claimFirst(CraftingManager crafting, Logger logger) {
List<IngredientType<?>> types = crafting.getIngredients();
for (int i = 1; i < types.size(); i++) {
if (ID.equals(types.get(i).getId())) {
types.add(0, types.remove(i));
logger.info("Moved the cooking station ingredient ahead of " + types.get(1).getId() + ".");
return;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package net.tfminecraft.cooking.mmoitems;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.bukkit.plugin.PluginManager;

/**
* Keeps MMOItems classes out of Cooking unless MMOItems is installed. Register from onLoad:
* MMOItems reads its crafting stations when it enables, and the ingredient type must exist by then.
*/
public final class MMOItemsSupport {
private MMOItemsSupport() {}

public static boolean registerIfPresent(PluginManager plugins, Logger logger) {
return run(plugins, logger, () -> MMOItemsIngredients.register(logger), "register");
}

/** Call once every plugin has enabled (the first server tick). */
public static boolean claimFirstIfPresent(PluginManager plugins, Logger logger) {
return run(plugins, logger, () -> MMOItemsIngredients.claimFirst(logger), "order");
}

private static boolean run(PluginManager plugins, Logger logger, Runnable step, String what) {
if (plugins.getPlugin("MMOItems") == null) {
return false;
}
try {
step.run();
return true;
} catch (RuntimeException | LinkageError e) {
logger.log(Level.WARNING, "Could not " + what + " the MMOItems cooking ingredient", e);
return false;
}
}
}
Loading
Loading