Skip to content
Open
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
27 changes: 27 additions & 0 deletions api/src/main/java/dev/jsinco/brewery/api/brew/BrewingStep.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ default Component infoDisplay(Brew.State state, TagResolver resolver) {
}, Argument.tagResolver(resolver));
}

/**
* How the brew will outcome be handled, whenever the brew is incomplete
*
* @return The behavior for handling incomplete brews
*/
IncompleteBehavior incompleteBehavior();

interface TimedStep extends BrewingStep {
/**
* @return The time for this step (ticks)
Expand Down Expand Up @@ -171,6 +178,11 @@ interface Distill extends AuthoredStep<Distill> {
* @return A new instance of this step with distill runs incremented by 1
*/
Distill incrementRuns();

@Override
default IncompleteBehavior incompleteBehavior() {
return IncompleteBehavior.INCOMPLETE;
}
}

interface Age extends TimedStep, AuthoredStep<Age> {
Expand All @@ -185,6 +197,11 @@ interface Age extends TimedStep, AuthoredStep<Age> {
* @return A new instance of this step with specified aging time
*/
Age withAge(Moment age);

@Override
default IncompleteBehavior incompleteBehavior() {
return IncompleteBehavior.INCOMPLETE;
}
}

interface Mix extends CauldronStep<Mix> {
Expand All @@ -199,6 +216,11 @@ interface Mix extends CauldronStep<Mix> {

@Override
Mix withTime(Moment time);

@Override
default IncompleteBehavior incompleteBehavior() {
return IncompleteBehavior.FAIL;
}
}

interface Cook extends CauldronStep<Cook> {
Expand All @@ -216,6 +238,11 @@ interface Cook extends CauldronStep<Cook> {
default Cook withBrewTime(Moment time) {
return withTime(time);
}

@Override
default IncompleteBehavior incompleteBehavior() {
return IncompleteBehavior.FAIL;
}
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package dev.jsinco.brewery.api.brew;

public enum IncompleteBehavior {
INCOMPLETE,
FAIL
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dev.jsinco.brewery.api.ingredient;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.translation.Argument;

public enum AlternateIngredientState {
/**
* Ingredient was added too late, it's considered raw and will fail the whole brew
*/
RAW("tbp.ingredient.state.raw");

private final String translationKey;

AlternateIngredientState(String translationKey) {
this.translationKey = translationKey;
}

Component toComponent(Component originalName) {
return Component.translatable(translationKey, Argument.component("ingredient", originalName));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.jsinco.brewery.api.ingredient;

import dev.jsinco.brewery.api.serialize.EnumSerializer;
import dev.jsinco.brewery.api.serialize.Serializer;
import dev.jsinco.brewery.api.util.BreweryKey;
import dev.jsinco.brewery.api.util.BreweryKeyed;
Expand All @@ -25,4 +26,12 @@ public record IngredientMeta<T>(BreweryKey key, Serializer<T> serializer) implem
Serializer.fork(MiniMessage.miniMessage()::serialize, MiniMessage.miniMessage()::deserialize, Component.class::isInstance,
new Serializer.StringMetaSerializer())
);

/**
* Alternate indications for ingredients, for example ingredient was added too late; "it's fucking raw" (Gordon Ramsay)
*/
public static IngredientMeta<AlternateIngredientState> ALTERNATE_STATE = new IngredientMeta<>(
BreweryKey.parse("alternate_state"),
new EnumSerializer<>(AlternateIngredientState.class)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
Expand All @@ -27,10 +28,16 @@ public record IngredientWithMeta(Ingredient ingredient,
@Override
public @NonNull Component displayName() {
Component override = get(IngredientMeta.DISPLAY_NAME);
Component output;
if (override == null) {
return ingredient.displayName();
output = ingredient.displayName();
} else {
output = override;
}
return override;
if (get(IngredientMeta.ALTERNATE_STATE) instanceof AlternateIngredientState alternateIngredientState) {
output = alternateIngredientState.toComponent(output);
}
return output;
}

@Override
Expand Down Expand Up @@ -72,4 +79,11 @@ public IngredientWithMeta applyTo(Ingredient ingredient) {
public <T> @Nullable T getOrDefault(IngredientMeta<T> metaKey, T defaultValue) {
return (T) meta.getOrDefault(metaKey, defaultValue);
}

public <T> IngredientWithMeta withMeta(IngredientMeta<T> ingredientMeta, T value) {
Preconditions.checkArgument(ingredientMeta.serializer().appliesTo(value), "Invalid ingredient meta value: %s".formatted(value));
Map<IngredientMeta<?>, Object> all = new HashMap<>(meta);
all.put(ingredientMeta, value);
return new IngredientWithMeta(this.ingredient, Map.copyOf(all));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dev.jsinco.brewery.api.serialize;

import java.util.Arrays;
import java.util.Locale;

public record EnumSerializer<E extends Enum<E>>(Class<E> eClass) implements Serializer<E> {
@Override
public E deserialize(String serialized) {
return Arrays.stream(eClass.getEnumConstants())
.filter(enumConstant -> enumConstant.name().equalsIgnoreCase(serialized))
.findFirst()
.orElse(null);
}

@Override
public String serializeSafely(E deserialized) {
return deserialized.name().toLowerCase(Locale.ROOT);
}

@Override
public boolean appliesTo(Object object) {
return eClass.isInstance(object);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

import dev.jsinco.brewery.api.brew.Brew;
import dev.jsinco.brewery.api.brew.BrewingStep;
import dev.jsinco.brewery.api.brew.IncompleteBehavior;
import dev.jsinco.brewery.api.breweries.Cauldron;
import dev.jsinco.brewery.api.breweries.CauldronType;
import dev.jsinco.brewery.api.ingredient.AlternateIngredientState;
import dev.jsinco.brewery.api.ingredient.Ingredient;
import dev.jsinco.brewery.api.ingredient.IngredientMeta;
import dev.jsinco.brewery.api.ingredient.IngredientWithMeta;
import dev.jsinco.brewery.api.moment.Interval;
import dev.jsinco.brewery.api.moment.Moment;
import dev.jsinco.brewery.api.recipe.DefaultRecipe;
Expand Down Expand Up @@ -184,13 +188,18 @@ private Color computeParticleColor(Color baseColor, Color resultColor, Recipe<It
}

private Color computeResultColor(Optional<Recipe<ItemStack>> recipeOptional) {
if (recipeOptional.isEmpty()) {
if (recipeOptional.isEmpty() || matcherResult.quality().isEmpty()) {
return convert(Config.config().cauldrons().failedParticleColor());
}
RecipeResult<ItemStack> recipeResult = matcherResult.recipeResult().orElse(null);
if (matcherResult.score().completed() && recipeResult != null) {
return Color.fromRGB(recipeResult.brewColor().getRGB() & 0xFFFFFF);
}
if (!matcherResult.score().completed()
&& recipeOptional.get().getSteps().size() == matcherResult.matchingSteps().size()
&& recipeOptional.get().getSteps().getLast().incompleteBehavior() == IncompleteBehavior.FAIL) {
return Color.fromRGB(recipeOptional.get().getRecipeResult(matcherResult.score().brewQuality()).brewColor().getRGB() & 0xFFFFFF);
}
List<DefaultRecipe<ItemStack>> defaultRecipes = new ArrayList<>(BrewAdapterAccess.getPossibleDefaultRecipes(
recipeOptional.orElse(null),
TheBrewingProject.getInstance().getRecipeRegistry(),
Expand Down Expand Up @@ -265,25 +274,45 @@ public boolean withIngredient(@NonNull ItemStack item, Player player) {
private Brew withIngredient(Ingredient ingredient) {
long time = TheBrewingProject.getInstance().getTime();
Brew newBrew;
final Ingredient finalIngredient;
boolean raw = !brew.getCompletedSteps().isEmpty() && brew.lastCompletedStep() instanceof BrewingStep.CauldronStep<?> cauldronStep
&& cauldronStep.time().moment() > Config.config().cauldrons().cookingMinuteTicks();
if (raw) {
if (ingredient instanceof IngredientWithMeta ingredientWithMeta) {
finalIngredient = ingredientWithMeta.withMeta(IngredientMeta.ALTERNATE_STATE, AlternateIngredientState.RAW);
} else {
finalIngredient = new IngredientWithMeta(ingredient, Map.of(IngredientMeta.ALTERNATE_STATE, AlternateIngredientState.RAW));
}
} else {
finalIngredient = ingredient;
}
if (hot) {
newBrew = brew.withLastStep(BrewingStep.Cook.class,
cook -> {
Map<Ingredient, Integer> ingredients = new HashMap<>(cook.ingredients());
int amount = ingredients.computeIfAbsent(ingredient, ignored -> 0);
ingredients.put(ingredient, amount + 1);
int amount = ingredients.computeIfAbsent(finalIngredient, ignored -> 0);
ingredients.put(finalIngredient, amount + 1);
if (Config.config().cauldrons().resetCookTimeOnIngredientAdd() && !raw) {
return cook.withIngredients(ingredients)
.withTime(new Interval(time, time));
}
return cook.withIngredients(ingredients);
},
() -> new CookStepImpl(new Interval(time, time), Map.of(ingredient, 1), cauldronType)
() -> new CookStepImpl(new Interval(time, time), Map.of(finalIngredient, 1), cauldronType)
);
} else {
newBrew = brew.withLastStep(BrewingStep.Mix.class,
mix -> {
Map<Ingredient, Integer> ingredients = new HashMap<>(mix.ingredients());
int amount = ingredients.computeIfAbsent(ingredient, ignored -> 0);
ingredients.put(ingredient, amount + 1);
int amount = ingredients.computeIfAbsent(finalIngredient, ignored -> 0);
ingredients.put(finalIngredient, amount + 1);
if (Config.config().cauldrons().resetCookTimeOnIngredientAdd() && !raw) {
return mix.withIngredients(ingredients)
.withTime(new Interval(time, time));
}
return mix.withIngredients(ingredients);
},
() -> new MixStepImpl(new Interval(time, time), Map.of(ingredient, 1), cauldronType)
() -> new MixStepImpl(new Interval(time, time), Map.of(finalIngredient, 1), cauldronType)
);
}
return newBrew;
Expand Down Expand Up @@ -311,9 +340,16 @@ private boolean handleBrewReaddition(ItemStack item, Brew addedBrew, Player play
merged = Optional.of(addedBrew.withStep(newStep()));
} else {
BrewingStep thisStep = existing.removeLast();
final BrewingStep stepToAdd;
if (thisStep instanceof BrewingStep.CauldronStep<?> cauldronStep) {
long time = TheBrewingProject.getInstance().getTime();
stepToAdd = cauldronStep.withTime(new Interval(time, time));
} else {
stepToAdd = thisStep;
}
List<BrewingStep> added = new ArrayList<>(addedBrew.getCompletedSteps());
merged = BrewUtil.mergeSteps(existing, added)
.map(steps -> Stream.concat(steps.stream(), Stream.of(thisStep)))
.map(steps -> Stream.concat(steps.stream(), Stream.of(stepToAdd)))
.map(Stream::toList)
.map(this.brew::withStepsReplaced);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import dev.jsinco.brewery.api.brew.BrewQuality;
import dev.jsinco.brewery.api.brew.BrewScore;
import dev.jsinco.brewery.api.brew.BrewingStep;
import dev.jsinco.brewery.api.brew.IncompleteBehavior;
import dev.jsinco.brewery.api.ingredient.Ingredient;
import dev.jsinco.brewery.api.ingredient.IngredientManager;
import dev.jsinco.brewery.api.recipe.DefaultRecipe;
Expand Down Expand Up @@ -74,7 +75,11 @@ public ItemStack toItem(Brew.State state) {
public ItemStack toItem(Brew.State state, @Nullable BrewQuality overrideQuality) {
RecipeRegistryImpl<ItemStack> recipeRegistry = TheBrewingProject.getInstance().getRecipeRegistry();
ItemStack itemStack;
if (overrideQuality == null || recipe == null) {
if (overrideQuality == null || recipe == null
|| (!score.completed()
&& recipe.getSteps().size() == matchingSteps.size()
&& recipe.getSteps().getLast().incompleteBehavior() == IncompleteBehavior.FAIL)
) {
itemStack = fromDefaultRecipe(recipe, recipeRegistry, brew, state, true);
itemStack.editPersistentDataContainer(pdc -> {
pdc.set(BrewAdapterAccess.BREWERY_SCORE, PersistentDataType.DOUBLE, 0D);
Expand Down
34 changes: 32 additions & 2 deletions core/src/main/java/dev/jsinco/brewery/brew/CookStepImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import dev.jsinco.brewery.api.brew.PartialBrewScore;
import dev.jsinco.brewery.api.brew.ScoreType;
import dev.jsinco.brewery.api.breweries.CauldronType;
import dev.jsinco.brewery.api.ingredient.AlternateIngredientState;
import dev.jsinco.brewery.api.ingredient.Ingredient;
import dev.jsinco.brewery.api.ingredient.IngredientMeta;
import dev.jsinco.brewery.api.ingredient.IngredientWithMeta;
import dev.jsinco.brewery.api.moment.Moment;
import dev.jsinco.brewery.api.moment.PassedMoment;
import dev.jsinco.brewery.util.BrewUtil;
Expand Down Expand Up @@ -55,7 +58,17 @@ public Map<ScoreType, PartialBrewScore> proximityScores(BrewingStep other) {
return BREW_STEP_MISMATCH;
}
double cauldronTypeScore = (cauldronType == null || otherType == null) ? 1D : cauldronType.appliesTo(otherType) ? 1D : 0D;
double timeScore = Math.sqrt(BrewingStepUtil.nearbyValueScore(this.time.moment(), otherTime.moment()));
double timeScore;
if (otherIngredients.keySet().stream()
.filter(IngredientWithMeta.class::isInstance)
.map(IngredientWithMeta.class::cast)
.flatMap(ingredient -> Optional.ofNullable(ingredient.get(IngredientMeta.ALTERNATE_STATE)).stream())
.anyMatch(alternateIngredientState -> alternateIngredientState == AlternateIngredientState.RAW)
) {
timeScore = 0D;
} else {
timeScore = Math.sqrt(BrewingStepUtil.nearbyValueScore(this.time.moment(), otherTime.moment()));
}
double ingredientsScore = BrewingStepUtil.getIngredientsScore((Map<Ingredient, Integer>) this.ingredients, (Map<Ingredient, Integer>) otherIngredients);
return Stream.of(
new PartialBrewScore(cauldronTypeScore * timeScore, ScoreType.TIME),
Expand All @@ -70,7 +83,24 @@ public StepType stepType() {

@Override
public Map<ScoreType, PartialBrewScore> maximumScores(BrewingStep other) {
return proximityScores(other);
if (!(other instanceof CookStepImpl(
Moment ignored1, Map<? extends Ingredient, Integer> otherIngredients,
CauldronType otherType, SequencedSet<UUID> ignored2, int ignored3
))) {
return BREW_STEP_MISMATCH;
}
double cauldronTypeScore = (cauldronType == null || otherType == null) ? 1D : cauldronType.appliesTo(otherType) ? 1D : 0D;
double timeScore = (otherIngredients.keySet().stream()
.filter(IngredientWithMeta.class::isInstance)
.map(IngredientWithMeta.class::cast)
.flatMap(ingredient -> Optional.ofNullable(ingredient.get(IngredientMeta.ALTERNATE_STATE)).stream())
.anyMatch(alternateIngredientState -> alternateIngredientState == AlternateIngredientState.RAW)
) ? 0 : 1D;
double ingredientsScore = BrewingStepUtil.getIngredientsScore((Map<Ingredient, Integer>) this.ingredients, (Map<Ingredient, Integer>) otherIngredients);
return Stream.of(
new PartialBrewScore(cauldronTypeScore * timeScore, ScoreType.TIME),
new PartialBrewScore(ingredientsScore, ScoreType.INGREDIENTS)
).collect(Collectors.toUnmodifiableMap(PartialBrewScore::type, partial -> partial));
}

@Override
Expand Down
Loading