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
32 changes: 32 additions & 0 deletions src/main/java/net/tfminecraft/cooking/brewery/BrewLiquid.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package net.tfminecraft.cooking.brewery;

import java.util.function.Function;
import java.util.regex.Pattern;

import org.bukkit.inventory.ItemStack;

/** A finished brew as the saucepan sees it. The reader is set only while BreweryX is enabled. */
public record BrewLiquid(String name, int stars, String colour, boolean alcoholic) {
private static final Pattern FORMATTING = Pattern.compile("(?i)[&§](#[0-9a-f]{6}|[0-9a-fk-orx])");
private static Function<ItemStack, BrewLiquid> reader;

public static void setReader(Function<ItemStack, BrewLiquid> value) {
reader = value;
}

/** The held brew, or null when the item is not a finished brew or BreweryX is not running. */
public static BrewLiquid from(ItemStack item) {
Function<ItemStack, BrewLiquid> current = reader;
return current == null || item == null ? null : current.apply(item);
}

/** Brewery rates 1-10 and shows half stars. A half star rounds up, like Cooking's ingredient averages. */
public static int starsFor(int breweryQuality) {
return Math.clamp((breweryQuality + 1) / 2, 1, 5);
}

/** Recipe names such as "&6Golden Mead" keep their words but not their colour codes. */
public static String plainName(String name) {
return name == null ? "" : FORMATTING.matcher(name).replaceAll("").strip();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package net.tfminecraft.cooking.brewery;

import org.bukkit.Color;
import org.bukkit.inventory.ItemStack;

import com.dre.brewery.Brew;
import com.dre.brewery.api.BreweryApi;
import com.dre.brewery.recipe.BRecipe;
import com.dre.brewery.recipe.PotionColor;

/** Reads BreweryX brews for the saucepan. Only loaded while BreweryX is enabled. */
final class BrewLiquidReader {
private BrewLiquidReader() {}

static BrewLiquid read(ItemStack item) {
Brew brew = BreweryApi.getBrew(item);
return brew == null ? null : describe(brew.getCurrentRecipe(), brew.getQuality());
}

/** A brew still fermenting has no recipe yet, so it is not a finished drink. */
static BrewLiquid describe(BRecipe recipe, int quality) {
if (recipe == null) return null;
return new BrewLiquid(BrewLiquid.plainName(recipe.getName(quality)), BrewLiquid.starsFor(quality),
hex(recipe.getColor()), recipe.getAlcohol() > 0);
}

private static String hex(PotionColor colour) {
Color rgb = colour == null ? null : colour.getColor();
return rgb == null ? null : String.format("%06x", rgb.asRGB());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ public static Runnable register(JavaPlugin plugin) {
Runnable cleanup = () -> {
HandlerList.unregisterAll(listener);
BCauldronRecipe.acceptedCustom.removeIf(item -> item == listener.gate);
BrewLiquid.setReader(null);
};
try {
plugin.getServer().getPluginManager().registerEvents(listener, plugin);
} catch (RuntimeException | LinkageError exception) {
cleanup.run();
throw exception;
}
BrewLiquid.setReader(BrewLiquidReader::read);
return cleanup;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import net.tfminecraft.tlibs.objects.api.subapi.StringFormatter;

import net.tfminecraft.cooking.brewery.BrewLiquid;
import net.tfminecraft.cooking.cache.ItemCache;

import net.tfminecraft.cooking.cup.BucketItems;
Expand Down Expand Up @@ -79,8 +80,10 @@ public class SauceReference extends CookingReference {

private static final String VAR_COLOURS = "sauce.colours";
private static final String VAR_LIQUID = "sauce.liquid";
private static final String VAR_BREW = "sauce.brew";

private FoodItem liquidSource;
private BrewLiquid brew;

public SauceReference(Furniture f, Method m) {

Expand Down Expand Up @@ -244,7 +247,8 @@ public void scoop(Player p, ItemStack ladle) {

for (String c : colours) {

if (c.equalsIgnoreCase("ffffff")) {
// A white brew (vodka) is not milk.
if (brew == null && c.equalsIgnoreCase("ffffff")) {

hasMilk = true;

Expand Down Expand Up @@ -300,6 +304,13 @@ public void scoop(Player p, ItemStack ladle) {

sauce.addOrModifyTrack(cookedTrack);

TagTrack reduced = brew == null ? null : TrackLoader.getByString("reduced");
if (reduced != null) {
reduced = new TagTrack(reduced);
reduced.setValue(0);
sauce.addOrModifyTrack(reduced);
}



List<FoodItem> inputs = new ArrayList<>();
Expand Down Expand Up @@ -372,6 +383,11 @@ public void interact(FurnitureInteractEvent e) {

}

BrewLiquid pouredBrew = secondaries.containsKey("liquid") ? null : BrewLiquid.from(item);
if (pouredBrew != null) {
pourBrew(p, pouredBrew);
return;
}
if (!secondaries.containsKey("liquid") && isWrongSaucepanWater(item)) {

p.sendMessage("Use a cup of water to fill the saucepan.");
Expand Down Expand Up @@ -439,6 +455,47 @@ public void interact(FurnitureInteractEvent e) {



/** A finished alcoholic brew replaces the water or milk, so both add-in slots stay free. */
private void pourBrew(Player p, BrewLiquid poured) {
if (!poured.alcoholic()) {
p.sendMessage("§cOnly alcoholic brews can go in the saucepan.");
return;
}
if (f.getType() == null || f.getType().getSlot("liquid") == null) return;
brew = poured;
liquidSource = brewIngredient(poured);
saveLiquid(null);
f.getVariables().put(VAR_BREW, poured.stars() + ":" + poured.name());
secondaries.put("liquid", -1);
if (poured.colour() != null) addColour(poured.colour());
saveColours();
HeldStack.useOneKeepStack(p, new ItemStack(Material.GLASS_BOTTLE));
p.swingMainHand();
updateModel();
danger = 0;
f.getLoc().getWorld().playSound(f.getLoc(), Sound.ITEM_BOTTLE_EMPTY, 1f, 1f);
}

/** The brew counts toward the sauce's stars like the water or milk it replaces. */
private static FoodItem brewIngredient(BrewLiquid poured) {
FoodItem ingredient = FoodParser.parse("sauce(type=sauce;origin=Mixed)").template;
ingredient.setCategory("alcohol");
ingredient.setOrigin(poured.name());
ingredient.setQualityRange(poured.stars(), poured.stars());
return ingredient;
}

/** The brew leads the sauce's name, e.g. "Red Wine Sauce". */
@Override
public List<String> getIngredients() {
List<String> names = super.getIngredients();
if (brew == null || brew.name().isEmpty()) return names;
List<String> withBrew = new ArrayList<>();
withBrew.add(brew.name());
withBrew.addAll(names);
return withBrew;
}

public void updateModel() {

String path = getLiquidItemPath();
Expand All @@ -452,10 +509,12 @@ public void updateModel() {
@Override
public void clear() {
liquidSource = null;
brew = null;
super.clear();
if (f == null) return;
f.getVariables().remove(VAR_COLOURS);
f.getVariables().remove(VAR_LIQUID);
f.getVariables().remove(VAR_BREW);
InteractibleFurniture.getInstance().getFurnitureManager().markDirty(f);
}

Expand All @@ -472,6 +531,8 @@ public void rebuildFromFurniture() {
secondaries.put("liquid", -1);
ItemStack liquid = PotReference.decodeStack(stringVariable(VAR_LIQUID));
liquidSource = liquid == null ? null : FoodItem.fromItem(liquid);
brew = restoreBrew(stringVariable(VAR_BREW));
if (brew != null) liquidSource = brewIngredient(brew);
restoreColours();
}

Expand All @@ -498,6 +559,17 @@ private void restoreColours() {
}
}

/** Saved as "stars:name". Names may contain colons, the star count never does. */
private static BrewLiquid restoreBrew(String saved) {
int split = saved == null ? -1 : saved.indexOf(':');
if (split <= 0) return null;
try {
return new BrewLiquid(saved.substring(split + 1), Integer.parseInt(saved.substring(0, split)), null, true);
} catch (NumberFormatException invalid) {
return null;
}
}

private String stringVariable(String key) {
return f.getVariables().get(key) instanceof String text ? text : null;
}
Expand Down
8 changes: 8 additions & 0 deletions src/main/resources/tags.yml
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,11 @@ rising:
name: "#e6c98aRisen"
value: 900
stars: 0

# A BreweryX brew poured into the saucepan as its liquid.
reduced:
reduced:
name: "#a8455aReduced"
value: 0
nutrition-mult: 1.10
food-mult: 1.10
70 changes: 70 additions & 0 deletions src/test/java/net/tfminecraft/cooking/brewery/BrewLiquidTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package net.tfminecraft.cooking.brewery;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;

import com.dre.brewery.api.BreweryApi;
import com.dre.brewery.recipe.BRecipe;
import com.dre.brewery.recipe.PotionColor;

class BrewLiquidTest {
@AfterEach void resetReader() { BrewLiquid.setReader(null); }

@Test void breweryQualityRoundsHalfStarsUp() {
int[] expected = {1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5};
for (int quality = 0; quality <= 11; quality++) {
assertEquals(expected[quality], BrewLiquid.starsFor(quality), "quality " + quality);
}
assertEquals(1, BrewLiquid.starsFor(-3));
}

@Test void recipeNamesLoseTheirColourCodes() {
assertEquals("Golden Mead", BrewLiquid.plainName("&6Golden Mead"));
assertEquals("Shimmering Golden Vodka", BrewLiquid.plainName(" §6§lShimmering &#ffaa00Golden Vodka "));
assertEquals("Red Wine", BrewLiquid.plainName("Red Wine"));
assertEquals("", BrewLiquid.plainName(null));
}

@Test void withoutBreweryNothingIsABrew() {
ItemStack potion = new ItemStack(Material.POTION);
assertNull(BrewLiquid.from(potion));
BrewLiquid wine = new BrewLiquid("Red Wine", 5, "ff0000", true);
BrewLiquid.setReader(item -> wine);
assertSame(wine, BrewLiquid.from(potion));
assertNull(BrewLiquid.from(null));
}

@Test void readsTheRecipeNameStarsColourAndAlcohol() {
BRecipe recipe = mock(BRecipe.class);
PotionColor colour = mock(PotionColor.class);
assertNull(BrewLiquidReader.describe(null, 7), "Still fermenting, no recipe yet");

when(recipe.getName(9)).thenReturn("&6Golden Mead");
when(recipe.getAlcohol()).thenReturn(9);
when(recipe.getColor()).thenReturn(colour);
when(colour.getColor()).thenReturn(Color.fromRGB(0xff9900));
assertEquals(new BrewLiquid("Golden Mead", 5, "ff9900", true), BrewLiquidReader.describe(recipe, 9));

when(recipe.getAlcohol()).thenReturn(-6);
when(colour.getColor()).thenReturn(null);
assertEquals(new BrewLiquid("Golden Mead", 5, null, false), BrewLiquidReader.describe(recipe, 9));

when(recipe.getColor()).thenReturn(null);
assertNull(BrewLiquidReader.describe(recipe, 9).colour());
}

@Test void itemsThatAreNotBrewsAreNotLiquids() {
ItemStack potion = new ItemStack(Material.POTION);
try (MockedStatic<BreweryApi> api = mockStatic(BreweryApi.class)) {
assertNull(BrewLiquidReader.read(potion));
api.verify(() -> BreweryApi.getBrew(potion));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,17 @@ class BreweryIngredientTest {
bridge.close();bridge.close();
}

@Test void registrationLetsTheSaucepanReadBrewsUntilClosed() {
ItemStack potion = new ItemStack(Material.POTION);
try (MockedStatic<BreweryApi> api = mockStatic(BreweryApi.class)) {
assertNull(BrewLiquid.from(potion));
api.verify(() -> BreweryApi.getBrew(potion), times(1));
cleanup.run();
assertNull(BrewLiquid.from(potion));
api.verify(() -> BreweryApi.getBrew(potion), times(1));
}
}

@Test void failedListenerRegistrationRemovesTheGate() {
JavaPlugin owner = mock(JavaPlugin.class, RETURNS_DEEP_STUBS);
var manager = owner.getServer().getPluginManager();
Expand Down
Loading
Loading