diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fa590d4d..05fc93c56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,17 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added - Autocrafting engine. +- The crafting preview now has the ability to fill out the maximum amount of a resource you can currently craft. ### Changed - Autocrafting now handles multiple patterns with the same output correctly by trying to use the pattern with the highest priority first. If there are missing resources, lower priority patterns are checked. +### Fixed + +- Fixed amount in amount screens resetting when resizing the screen. + ## [2.0.0-milestone.4.11] - 2024-12-08 ### Added diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CalculationException.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CalculationException.java new file mode 100644 index 000000000..1783b1b1d --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CalculationException.java @@ -0,0 +1,7 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +class CalculationException extends RuntimeException { + protected CalculationException(final String message) { + super(message); + } +} diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculator.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculator.java index ec3f665ef..a961ef919 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculator.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculator.java @@ -4,8 +4,9 @@ import org.apiguardian.api.API; -@FunctionalInterface @API(status = API.Status.STABLE, since = "2.0.0-milestone.4.12") public interface CraftingCalculator { void calculate(ResourceKey resource, long amount, CraftingCalculatorListener listener); + + long getMaxAmount(ResourceKey resource); } diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculatorImpl.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculatorImpl.java index 4661bb873..f40065405 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculatorImpl.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculatorImpl.java @@ -8,9 +8,14 @@ import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import static com.refinedmods.refinedstorage.api.autocrafting.calculation.CraftingTree.root; public class CraftingCalculatorImpl implements CraftingCalculator { + private static final Logger LOGGER = LoggerFactory.getLogger(CraftingCalculatorImpl.class); + private final PatternRepository patternRepository; private final RootStorage rootStorage; @@ -29,6 +34,9 @@ public void calculate(final ResourceKey resource, Amount lastPatternAmount = null; for (final Pattern pattern : patterns) { final Amount patternAmount = Amount.of(pattern, resource, amount); + if (patternAmount.getTotal() < 0) { + throw new NumberOverflowDuringCalculationException(); + } final CraftingCalculatorListener childListener = listener.childCalculationStarted(); final CraftingTree tree = root(pattern, rootStorage, patternAmount, patternRepository, childListener); final CraftingTree.CalculationResult calculationResult = tree.calculate(); @@ -45,4 +53,47 @@ public void calculate(final ResourceKey resource, } listener.childCalculationCompleted(resource, lastPatternAmount.getTotal(), lastChildListener); } + + private boolean isCraftable(final ResourceKey resource, final long amount) { + final MissingResourcesCraftingCalculatorListener listener = new MissingResourcesCraftingCalculatorListener(); + calculate(resource, amount, listener); + return !listener.isMissingResources(); + } + + @Override + public long getMaxAmount(final ResourceKey resource) { + try { + LOGGER.debug("Finding max amount for {} starting from 1", resource); + long low = 1; + long high = 1; + int calculationCount = 1; + while (isCraftable(resource, high)) { + low = high; + high = high * 2; + LOGGER.debug("Finding low and high for the craftable amount, currently between {} and {}", low, high); + calculationCount++; + } + if (low == high) { + return 0; + } + LOGGER.debug("Our craftable amount is between {} and {}", low, high); + while (low < high) { + final long amount = low + (high - low + 1) / 2; + LOGGER.debug("Trying {} (between {} and {})", amount, low, high); + calculationCount++; + if (isCraftable(resource, amount)) { + LOGGER.debug("{} was craftable, increasing our low amount", amount); + low = amount; + } else { + LOGGER.debug("{} is not craftable, decreasing our high amount", amount); + high = amount - 1; + } + } + LOGGER.debug("Found the maximum amount of {} in {} tries", low, calculationCount); + return low; + } catch (final CalculationException e) { + LOGGER.debug("Failed to calculate the maximum amount", e); + return 0; + } + } } diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingState.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingState.java index 6e287893b..cc4acc9c5 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingState.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingState.java @@ -1,6 +1,7 @@ package com.refinedmods.refinedstorage.api.autocrafting.calculation; import com.refinedmods.refinedstorage.api.autocrafting.Pattern; +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; import com.refinedmods.refinedstorage.api.resource.ResourceKey; import com.refinedmods.refinedstorage.api.resource.list.MutableResourceList; import com.refinedmods.refinedstorage.api.resource.list.MutableResourceListImpl; @@ -28,10 +29,18 @@ void extractFromStorage(final ResourceKey resource, final long amount) { void addOutputsToInternalStorage(final Pattern pattern, final Amount amount) { pattern.getOutputs().forEach( - output -> internalStorage.add(output.resource(), output.amount() * amount.iterations()) + output -> addOutputToInternalStorage(amount, output) ); } + private void addOutputToInternalStorage(final Amount amount, final ResourceAmount output) { + final long totalAmount = output.amount() * amount.iterations(); + if (totalAmount < 0) { + throw new NumberOverflowDuringCalculationException(); + } + internalStorage.add(output.resource(), totalAmount); + } + CraftingState copy() { return new CraftingState(storage.copy(), internalStorage.copy()); } diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingTree.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingTree.java index 2479adf2f..755bacac9 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingTree.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingTree.java @@ -5,7 +5,9 @@ import com.refinedmods.refinedstorage.api.autocrafting.PatternRepository; import com.refinedmods.refinedstorage.api.storage.root.RootStorage; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.annotation.Nullable; import static java.util.Objects.requireNonNull; @@ -15,18 +17,21 @@ class CraftingTree { private final Amount amount; private final PatternRepository patternRepository; private final CraftingCalculatorListener listener; + private final Set activePatterns; private CraftingState craftingState; private CraftingTree(final Pattern pattern, final CraftingState craftingState, final Amount amount, final PatternRepository patternRepository, - final CraftingCalculatorListener listener) { + final CraftingCalculatorListener listener, + final Set activePatterns) { this.pattern = pattern; this.craftingState = craftingState; this.amount = amount; this.patternRepository = patternRepository; this.listener = listener; + this.activePatterns = activePatterns; } static CraftingTree root(final Pattern pattern, @@ -35,19 +40,23 @@ static CraftingTree root(final Pattern pattern, final PatternRepository patternRepository, final CraftingCalculatorListener listener) { final CraftingState craftingState = CraftingState.of(rootStorage); - return new CraftingTree<>(pattern, craftingState, amount, patternRepository, listener); + return new CraftingTree<>(pattern, craftingState, amount, patternRepository, listener, new HashSet<>()); } static CraftingTree child(final Pattern pattern, final CraftingState parentState, final Amount amount, final PatternRepository patternRepository, - final CraftingCalculatorListener listener) { + final CraftingCalculatorListener listener, + final Set activePatterns) { return new CraftingTree<>(pattern, parentState.copy(), amount, patternRepository, - listener.childCalculationStarted()); + listener.childCalculationStarted(), activePatterns); } CalculationResult calculate() { + if (!activePatterns.add(pattern)) { + throw new PatternCycleDetectedException(pattern); + } CalculationResult result = CalculationResult.SUCCESS; for (final Ingredient ingredient : pattern.getIngredients()) { if (ingredient.isEmpty()) { @@ -60,12 +69,16 @@ CalculationResult calculate() { } } craftingState.addOutputsToInternalStorage(pattern, amount); + activePatterns.remove(pattern); return result; } private CalculationResult calculateIngredient(final IngredientState ingredientState) { CraftingState.ResourceState resourceState = craftingState.getResource(ingredientState.get()); long remaining = ingredientState.amount() * amount.iterations(); + if (remaining < 0) { + throw new NumberOverflowDuringCalculationException(); + } while (remaining > 0) { if (resourceState.isInInternalStorage()) { final long toTake = Math.min(remaining, resourceState.inInternalStorage()); @@ -147,7 +160,8 @@ private ChildCalculationResult calculateChild(final long remaining, craftingState, childAmount, patternRepository, - listener + listener, + activePatterns ); final CalculationResult childResult = childTree.calculate(); if (childResult == CalculationResult.MISSING_RESOURCES) { diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/MissingResourcesCraftingCalculatorListener.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/MissingResourcesCraftingCalculatorListener.java new file mode 100644 index 000000000..1b448dcad --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/MissingResourcesCraftingCalculatorListener.java @@ -0,0 +1,40 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +import com.refinedmods.refinedstorage.api.resource.ResourceKey; + +class MissingResourcesCraftingCalculatorListener implements CraftingCalculatorListener { + private boolean missingResources; + + MissingResourcesCraftingCalculatorListener() { + } + + MissingResourcesCraftingCalculatorListener(final boolean missingResources) { + this.missingResources = missingResources; + } + + boolean isMissingResources() { + return missingResources; + } + + @Override + public CraftingCalculatorListener childCalculationStarted() { + return new MissingResourcesCraftingCalculatorListener(missingResources); + } + + @Override + public void childCalculationCompleted(final ResourceKey resource, + final long amount, + final CraftingCalculatorListener childListener) { + missingResources = ((MissingResourcesCraftingCalculatorListener) childListener).missingResources; + } + + @Override + public void ingredientsExhausted(final ResourceKey resource, final long amount) { + missingResources = true; + } + + @Override + public void ingredientExtractedFromStorage(final ResourceKey resource, final long amount) { + // no op + } +} diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/NumberOverflowDuringCalculationException.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/NumberOverflowDuringCalculationException.java new file mode 100644 index 000000000..1ef3047a5 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/NumberOverflowDuringCalculationException.java @@ -0,0 +1,7 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +public class NumberOverflowDuringCalculationException extends CalculationException { + NumberOverflowDuringCalculationException() { + super("Invalid amount"); + } +} diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/PatternCycleDetectedException.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/PatternCycleDetectedException.java new file mode 100644 index 000000000..c8ebf4269 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/PatternCycleDetectedException.java @@ -0,0 +1,16 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +import com.refinedmods.refinedstorage.api.autocrafting.Pattern; + +public class PatternCycleDetectedException extends CalculationException { + private final Pattern pattern; + + PatternCycleDetectedException(final Pattern pattern) { + super("Pattern loop detected in pattern " + pattern); + this.pattern = pattern; + } + + public Pattern getPattern() { + return this.pattern; + } +} diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/Preview.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/Preview.java index 1ca73b92b..ad493b6d8 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/Preview.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/Preview.java @@ -1,9 +1,11 @@ package com.refinedmods.refinedstorage.api.autocrafting.preview; +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; + import java.util.List; import org.apiguardian.api.API; @API(status = API.Status.STABLE, since = "2.0.0-milestone.4.9") -public record Preview(PreviewType type, List items) { +public record Preview(PreviewType type, List items, List outputsOfPatternWithCycle) { } diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewBuilder.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewBuilder.java index 1c07fde3c..ce894747f 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewBuilder.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewBuilder.java @@ -1,15 +1,21 @@ package com.refinedmods.refinedstorage.api.autocrafting.preview; +import com.refinedmods.refinedstorage.api.autocrafting.Pattern; import com.refinedmods.refinedstorage.api.core.CoreValidations; +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; import com.refinedmods.refinedstorage.api.resource.ResourceKey; +import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; public class PreviewBuilder { private final PreviewType type; private final Map items = new LinkedHashMap<>(); + private List outputsOfPatternWithCycle = Collections.emptyList(); + private PreviewBuilder(final PreviewType type) { this.type = type; } @@ -22,6 +28,11 @@ private MutablePreviewItem get(final ResourceKey resource) { return items.computeIfAbsent(resource, key -> new MutablePreviewItem()); } + public PreviewBuilder withPatternWithCycle(final Pattern pattern) { + this.outputsOfPatternWithCycle = pattern.getOutputs(); + return this; + } + public PreviewBuilder addAvailable(final ResourceKey resource, final long amount) { CoreValidations.validateLargerThanZero(amount, "Available amount must be larger than 0"); get(resource).available += amount; @@ -44,7 +55,7 @@ public Preview build() { return new Preview(type, items.entrySet() .stream() .map(entry -> entry.getValue().toPreviewItem(entry.getKey())) - .toList()); + .toList(), outputsOfPatternWithCycle); } private static class MutablePreviewItem { diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewCraftingCalculatorListener.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewCraftingCalculatorListener.java index f57997532..31b1a6791 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewCraftingCalculatorListener.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewCraftingCalculatorListener.java @@ -1,10 +1,14 @@ package com.refinedmods.refinedstorage.api.autocrafting.preview; +import com.refinedmods.refinedstorage.api.autocrafting.calculation.CraftingCalculator; import com.refinedmods.refinedstorage.api.autocrafting.calculation.CraftingCalculatorListener; +import com.refinedmods.refinedstorage.api.autocrafting.calculation.NumberOverflowDuringCalculationException; +import com.refinedmods.refinedstorage.api.autocrafting.calculation.PatternCycleDetectedException; import com.refinedmods.refinedstorage.api.resource.ResourceKey; import com.refinedmods.refinedstorage.api.resource.list.MutableResourceList; import com.refinedmods.refinedstorage.api.resource.list.MutableResourceListImpl; +import java.util.Collections; import java.util.UUID; import org.slf4j.Logger; @@ -22,8 +26,18 @@ private PreviewCraftingCalculatorListener(final PreviewState previewState) { this.previewState = previewState; } - public static PreviewCraftingCalculatorListener ofRoot() { - return new PreviewCraftingCalculatorListener(new PreviewState()); + public static Preview calculatePreview(final CraftingCalculator calculator, + final ResourceKey resource, + final long amount) { + final PreviewCraftingCalculatorListener listener = new PreviewCraftingCalculatorListener(new PreviewState()); + try { + calculator.calculate(resource, amount, listener); + } catch (final PatternCycleDetectedException e) { + return new Preview(PreviewType.CYCLE_DETECTED, Collections.emptyList(), e.getPattern().getOutputs()); + } catch (final NumberOverflowDuringCalculationException e) { + return new Preview(PreviewType.OVERFLOW, Collections.emptyList(), Collections.emptyList()); + } + return listener.buildPreview(); } @Override diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewProvider.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewProvider.java index fd69071c9..c274f4a76 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewProvider.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewProvider.java @@ -10,5 +10,7 @@ public interface PreviewProvider { Optional getPreview(ResourceKey resource, long amount); + long getMaxAmount(ResourceKey resource); + boolean startTask(ResourceKey resource, long amount); } diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewType.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewType.java index 26e8d2b07..80f75d36e 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewType.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewType.java @@ -5,5 +5,7 @@ @API(status = API.Status.STABLE, since = "2.0.0-milestone.4.9") public enum PreviewType { SUCCESS, - MISSING_RESOURCES + MISSING_RESOURCES, + CYCLE_DETECTED, + OVERFLOW } diff --git a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/AutocraftingHelpers.java b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/AutocraftingHelpers.java new file mode 100644 index 000000000..05f423edf --- /dev/null +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/AutocraftingHelpers.java @@ -0,0 +1,30 @@ +package com.refinedmods.refinedstorage.api.autocrafting; + +import com.refinedmods.refinedstorage.api.core.Action; +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; +import com.refinedmods.refinedstorage.api.storage.EmptyActor; +import com.refinedmods.refinedstorage.api.storage.StorageImpl; +import com.refinedmods.refinedstorage.api.storage.root.RootStorage; +import com.refinedmods.refinedstorage.api.storage.root.RootStorageImpl; + +public final class AutocraftingHelpers { + private AutocraftingHelpers() { + } + + public static RootStorage storage(final ResourceAmount... resourceAmounts) { + final RootStorage storage = new RootStorageImpl(); + storage.addSource(new StorageImpl()); + for (final ResourceAmount resourceAmount : resourceAmounts) { + storage.insert(resourceAmount.resource(), resourceAmount.amount(), Action.EXECUTE, EmptyActor.INSTANCE); + } + return storage; + } + + public static PatternRepository patterns(final Pattern... patterns) { + final PatternRepository patternRepository = new PatternRepositoryImpl(); + for (final Pattern pattern : patterns) { + patternRepository.add(pattern); + } + return patternRepository; + } +} diff --git a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/PatternImpl.java b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/PatternImpl.java index 7f5a7066d..2bd80427f 100644 --- a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/PatternImpl.java +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/PatternImpl.java @@ -41,4 +41,9 @@ public List getIngredients() { public List getOutputs() { return outputs; } + + @Override + public String toString() { + return ingredients + " -> " + outputs; + } } diff --git a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculatorImplTest.java b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculatorImplTest.java new file mode 100644 index 000000000..e0b84def0 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculatorImplTest.java @@ -0,0 +1,95 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +import com.refinedmods.refinedstorage.api.autocrafting.PatternRepository; +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; +import com.refinedmods.refinedstorage.api.storage.root.RootStorage; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static com.refinedmods.refinedstorage.api.autocrafting.AutocraftingHelpers.patterns; +import static com.refinedmods.refinedstorage.api.autocrafting.AutocraftingHelpers.storage; +import static com.refinedmods.refinedstorage.api.autocrafting.PatternBuilder.pattern; +import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.CRAFTING_TABLE; +import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.OAK_LOG; +import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.OAK_PLANKS; +import static org.assertj.core.api.Assertions.assertThat; + +class CraftingCalculatorImplTest { + @Test + void shouldNotFindMaxAmountIfThereAreAlwaysMissingResources() { + // Arrange + final RootStorage storage = storage( + new ResourceAmount(OAK_PLANKS, 1) + ); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 4) + .build(), + pattern() + .ingredient(OAK_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final long maxAmount = sut.getMaxAmount(CRAFTING_TABLE); + + // Assert + assertThat(maxAmount).isZero(); + } + + @ParameterizedTest + @ValueSource(longs = {1L, 2L, 3L, 4L, 5L, 6L, 7L, 64L, 128L}) + void shouldFindMaxAmount(final long amountPossible) { + // Arrange + final RootStorage storage = storage( + new ResourceAmount(OAK_LOG, amountPossible) + ); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 4) + .build(), + pattern() + .ingredient(OAK_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final long maxAmount = sut.getMaxAmount(CRAFTING_TABLE); + + // Assert + assertThat(maxAmount).isEqualTo(amountPossible); + } + + @Test + void shouldNotFindMaxAmountIfThereIsANumberOverflow() { + // Arrange + final RootStorage storage = storage( + new ResourceAmount(OAK_PLANKS, Long.MAX_VALUE) + ); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 4) + .build(), + pattern() + .ingredient(OAK_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final long maxAmount = sut.getMaxAmount(CRAFTING_TABLE); + + // Assert + assertThat(maxAmount).isZero(); + } +} diff --git a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/package-info.java b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/package-info.java new file mode 100644 index 000000000..06a860405 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@FieldsAndMethodsAreNonnullByDefault +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +import com.refinedmods.refinedstorage.api.core.FieldsAndMethodsAreNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewBuilderTest.java b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewBuilderTest.java index af5fc6379..462f75c18 100644 --- a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewBuilderTest.java +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewBuilderTest.java @@ -1,5 +1,8 @@ package com.refinedmods.refinedstorage.api.autocrafting.preview; +import com.refinedmods.refinedstorage.api.autocrafting.Pattern; +import com.refinedmods.refinedstorage.api.autocrafting.PatternImpl; + import java.util.Collections; import java.util.List; @@ -27,7 +30,22 @@ void testDefaultState() { // Assert assertThat(preview).usingRecursiveComparison() - .isEqualTo(new Preview(PreviewType.SUCCESS, Collections.emptyList())); + .isEqualTo(new Preview(PreviewType.SUCCESS, Collections.emptyList(), Collections.emptyList())); + } + + @Test + void testWithPatternWithCycle() { + // Arrange + final Pattern pattern = new PatternImpl(OAK_PLANKS); + + // Act + final Preview preview = PreviewBuilder.ofType(PreviewType.CYCLE_DETECTED) + .withPatternWithCycle(pattern) + .build(); + + // Assert + assertThat(preview).usingRecursiveComparison() + .isEqualTo(new Preview(PreviewType.CYCLE_DETECTED, Collections.emptyList(), pattern.getOutputs())); } @Test @@ -49,7 +67,7 @@ void testPreview() { new PreviewItem(OAK_PLANKS, 0, 0, 5), new PreviewItem(OAK_LOG, 3, 0, 0), new PreviewItem(SPRUCE_LOG, 0, 3, 0) - ))); + ), Collections.emptyList())); } @ParameterizedTest diff --git a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewTest.java b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewTest.java index e389ea4b5..2071c8bdf 100644 --- a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewTest.java +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewTest.java @@ -2,16 +2,10 @@ import com.refinedmods.refinedstorage.api.autocrafting.Pattern; import com.refinedmods.refinedstorage.api.autocrafting.PatternRepository; -import com.refinedmods.refinedstorage.api.autocrafting.PatternRepositoryImpl; import com.refinedmods.refinedstorage.api.autocrafting.calculation.CraftingCalculator; import com.refinedmods.refinedstorage.api.autocrafting.calculation.CraftingCalculatorImpl; -import com.refinedmods.refinedstorage.api.core.Action; import com.refinedmods.refinedstorage.api.resource.ResourceAmount; -import com.refinedmods.refinedstorage.api.resource.ResourceKey; -import com.refinedmods.refinedstorage.api.storage.EmptyActor; -import com.refinedmods.refinedstorage.api.storage.StorageImpl; import com.refinedmods.refinedstorage.api.storage.root.RootStorage; -import com.refinedmods.refinedstorage.api.storage.root.RootStorageImpl; import java.util.stream.Stream; @@ -23,6 +17,8 @@ import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; +import static com.refinedmods.refinedstorage.api.autocrafting.AutocraftingHelpers.patterns; +import static com.refinedmods.refinedstorage.api.autocrafting.AutocraftingHelpers.storage; import static com.refinedmods.refinedstorage.api.autocrafting.PatternBuilder.pattern; import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.CRAFTING_TABLE; import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.OAK_LOG; @@ -31,7 +27,10 @@ import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.SPRUCE_LOG; import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.SPRUCE_PLANKS; import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.STICKS; +import static com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewCraftingCalculatorListener.calculatePreview; +import static com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewType.CYCLE_DETECTED; import static com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewType.MISSING_RESOURCES; +import static com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewType.OVERFLOW; import static com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewType.SUCCESS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -49,7 +48,7 @@ void shouldNotCalculateForPatternThatIsNotFound() { final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Executable action = () -> calculateAndGetPreview(sut, CRAFTING_TABLE, 1); + final Executable action = () -> calculatePreview(sut, CRAFTING_TABLE, 1); // Assert final IllegalStateException e = assertThrows(IllegalStateException.class, action); @@ -65,7 +64,7 @@ void shouldNotCalculateWithInvalidRequestedAmount(final long requestedAmount) { final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Executable action = () -> calculateAndGetPreview(sut, CRAFTING_TABLE, requestedAmount); + final Executable action = () -> calculatePreview(sut, CRAFTING_TABLE, requestedAmount); // Assert final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, action); @@ -88,7 +87,7 @@ void shouldCalculateForSingleRootPatternSingleIngredientAndAllResourcesAreAvaila final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, requestedAmount); + final Preview preview = calculatePreview(sut, CRAFTING_TABLE, requestedAmount); // Assert assertThat(preview).usingRecursiveComparison().isEqualTo(PreviewBuilder.ofType(SUCCESS) @@ -120,7 +119,7 @@ void shouldCalculateForSingleRootPatternSingleIngredientSpreadOutOverMultipleIng final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, requestedAmount); + final Preview preview = calculatePreview(sut, CRAFTING_TABLE, requestedAmount); // Assert assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(MISSING_RESOURCES) @@ -143,7 +142,7 @@ void shouldNotCalculateForSingleRootPatternSingleIngredientAndAlmostAllResources final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 3); + final Preview preview = calculatePreview(sut, CRAFTING_TABLE, 3); // Assert assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG) @@ -177,7 +176,7 @@ void shouldCalculateWithSingleRootPatternWithMultipleIngredientAndMultipleAreCra final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 1); + final Preview preview = calculatePreview(sut, CRAFTING_TABLE, 1); // Assert assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(SUCCESS) @@ -203,7 +202,7 @@ void shouldPrioritizeResourcesThatWeHaveMostOfInStorageForSingleRootPatternAndMu final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 11); + final Preview preview = calculatePreview(sut, CRAFTING_TABLE, 11); // Assert assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(SUCCESS) @@ -229,7 +228,7 @@ void shouldExhaustAllPossibleIngredientsWhenRunningOutInSingleRootPatternAndMult final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 16); + final Preview preview = calculatePreview(sut, CRAFTING_TABLE, 16); // Assert assertThat(preview) @@ -263,7 +262,7 @@ void shouldExhaustAllPossibleIngredientsWhenRunningOutInSingleRootPatternAndMult final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 1); + final Preview preview = calculatePreview(sut, CRAFTING_TABLE, 1); // Assert assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(MISSING_RESOURCES) @@ -292,7 +291,7 @@ void shouldCalculateForMultipleRootPatternsAndSingleIngredientAndAllResourcesAre final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 2); + final Preview preview = calculatePreview(sut, CRAFTING_TABLE, 2); // Assert assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(SUCCESS) @@ -320,7 +319,7 @@ void shouldNotCalculateForMultipleRootPatternsAndSingleIngredientAndAlmostAllRes final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 3); + final Preview preview = calculatePreview(sut, CRAFTING_TABLE, 3); // Assert assertThat(preview) @@ -352,7 +351,7 @@ void shouldCalculateForSingleRootPatternAndSingleChildPatternWithSingleIngredien final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 3); + final Preview preview = calculatePreview(sut, CRAFTING_TABLE, 3); // Assert assertThat(preview) @@ -388,7 +387,7 @@ void shouldNotCalculateForSingleRootPatternSingleChildPatternWSingleIngredientAn final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 3); + final Preview preview = calculatePreview(sut, CRAFTING_TABLE, 3); // Assert assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(MISSING_RESOURCES) @@ -426,7 +425,7 @@ void shouldCraftMoreIfNecessaryIfResourcesFromInternalStorageAreUsedUp() { final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, SIGN, 1); + final Preview preview = calculatePreview(sut, SIGN, 1); // Assert assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(SUCCESS) @@ -466,7 +465,7 @@ void shouldCraftMoreIfNecessaryIfResourcesFromStorageAreUsedUp() { final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, SIGN, 1); + final Preview preview = calculatePreview(sut, SIGN, 1); // Assert assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(SUCCESS) @@ -538,7 +537,7 @@ void shouldKeepCalculatingEvenIfResourcesAreMissing( final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, SIGN, requestedAmount); + final Preview preview = calculatePreview(sut, SIGN, requestedAmount); // Assert assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(expectedPreview); @@ -575,7 +574,7 @@ void shouldCraftCorrectAmountWhenNotRequestingAMultipleOfThePatternOutputAmount( final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); // Act - final Preview preview = calculateAndGetPreview(sut, OAK_PLANKS, requestedAmount); + final Preview preview = calculatePreview(sut, OAK_PLANKS, requestedAmount); // Assert assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(SUCCESS) @@ -584,28 +583,95 @@ void shouldCraftCorrectAmountWhenNotRequestingAMultipleOfThePatternOutputAmount( .build()); } - private static Preview calculateAndGetPreview(final CraftingCalculator calculator, - final ResourceKey resource, - final long amount) { - final PreviewCraftingCalculatorListener listener = PreviewCraftingCalculatorListener.ofRoot(); - calculator.calculate(resource, amount, listener); - return listener.buildPreview(); + @Test + void shouldDetectPatternCycles() { + // Arrange + final RootStorage storage = storage(); + final Pattern cycledPattern = pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 4) + .build(); + final PatternRepository patterns = patterns( + cycledPattern, + pattern() + .ingredient(OAK_PLANKS, 4) + .output(OAK_LOG, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculatePreview(sut, OAK_PLANKS, 1); + + // Assert + assertThat(preview).usingRecursiveComparison().isEqualTo(PreviewBuilder.ofType(CYCLE_DETECTED) + .withPatternWithCycle(cycledPattern) + .build()); + } + + @Test + void shouldDetectNumberOverflowInIngredient() { + // Arrange + final RootStorage storage = storage(); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, Long.MAX_VALUE) + .output(OAK_PLANKS, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculatePreview(sut, OAK_PLANKS, 2); + + // Assert + assertThat(preview).usingRecursiveComparison().isEqualTo(PreviewBuilder.ofType(OVERFLOW).build()); } - private static RootStorage storage(final ResourceAmount... resourceAmounts) { - final RootStorage storage = new RootStorageImpl(); - storage.addSource(new StorageImpl()); - for (final ResourceAmount resourceAmount : resourceAmounts) { - storage.insert(resourceAmount.resource(), resourceAmount.amount(), Action.EXECUTE, EmptyActor.INSTANCE); - } - return storage; + @Test + void shouldDetectNumberOverflowWithRootPattern() { + // Arrange + final RootStorage storage = storage(); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 4) + .build(), + pattern() + .ingredient(OAK_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculatePreview(sut, OAK_PLANKS, Long.MAX_VALUE); + + // Assert + assertThat(preview).usingRecursiveComparison().isEqualTo(PreviewBuilder.ofType(OVERFLOW).build()); } - private static PatternRepository patterns(final Pattern... patterns) { - final PatternRepository patternRepository = new PatternRepositoryImpl(); - for (final Pattern pattern : patterns) { - patternRepository.add(pattern); - } - return patternRepository; + @Test + void shouldDetectNumberOverflowWithOutputOfChildPattern() { + // Arrange + final RootStorage storage = storage(); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 4) + .output(SIGN, Long.MAX_VALUE) + .build(), + pattern() + .ingredient(OAK_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculatePreview(sut, CRAFTING_TABLE, 2); + + // Assert + assertThat(preview).usingRecursiveComparison().isEqualTo(PreviewBuilder.ofType(OVERFLOW).build()); } } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/AutocraftingPreviewContainerMenu.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/AutocraftingPreviewContainerMenu.java index 5bc763e4b..bee76a9ad 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/AutocraftingPreviewContainerMenu.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/AutocraftingPreviewContainerMenu.java @@ -2,10 +2,12 @@ import com.refinedmods.refinedstorage.api.autocrafting.preview.Preview; import com.refinedmods.refinedstorage.api.resource.ResourceAmount; +import com.refinedmods.refinedstorage.common.api.support.resource.PlatformResourceKey; import com.refinedmods.refinedstorage.common.api.support.resource.ResourceContainer; import com.refinedmods.refinedstorage.common.support.containermenu.AbstractResourceContainerMenu; import com.refinedmods.refinedstorage.common.support.containermenu.DisabledResourceSlot; import com.refinedmods.refinedstorage.common.support.containermenu.ResourceSlotType; +import com.refinedmods.refinedstorage.common.support.packet.c2s.C2SPackets; import com.refinedmods.refinedstorage.common.support.resource.ResourceContainerImpl; import java.util.ArrayList; @@ -104,4 +106,19 @@ public void responseReceived(final UUID id, final boolean started) { setCurrentRequest(requests.getFirst()); } } + + public void maxAmountResponseReceived(final long maxAmount) { + if (listener == null) { + return; + } + if (currentRequest.getResource() instanceof PlatformResourceKey resource) { + listener.maxAmountReceived(resource.getResourceType().getDisplayAmount(maxAmount)); + } + } + + void requestMaxAmount() { + if (currentRequest.getResource() instanceof PlatformResourceKey resource) { + C2SPackets.sendAutocraftingPreviewMaxAmountRequest(resource); + } + } } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/AutocraftingPreviewListener.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/AutocraftingPreviewListener.java index f2c528fbb..dc18fc16c 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/AutocraftingPreviewListener.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/AutocraftingPreviewListener.java @@ -10,4 +10,6 @@ interface AutocraftingPreviewListener { void previewChanged(@Nullable Preview preview); void requestRemoved(AutocraftingRequest request, boolean last); + + void maxAmountReceived(double maxAmount); } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/AutocraftingPreviewScreen.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/AutocraftingPreviewScreen.java index 9e83b6bad..c503766d3 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/AutocraftingPreviewScreen.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/preview/AutocraftingPreviewScreen.java @@ -3,11 +3,14 @@ import com.refinedmods.refinedstorage.api.autocrafting.preview.Preview; import com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewItem; import com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewType; +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; +import com.refinedmods.refinedstorage.common.Platform; import com.refinedmods.refinedstorage.common.api.RefinedStorageClientApi; import com.refinedmods.refinedstorage.common.api.support.resource.ResourceRendering; import com.refinedmods.refinedstorage.common.support.amount.AbstractAmountScreen; import com.refinedmods.refinedstorage.common.support.amount.AmountScreenConfiguration; import com.refinedmods.refinedstorage.common.support.amount.DoubleAmountOperations; +import com.refinedmods.refinedstorage.common.support.tooltip.HelpClientTooltipComponent; import com.refinedmods.refinedstorage.common.support.tooltip.SmallText; import com.refinedmods.refinedstorage.common.support.widget.ScrollbarWidget; @@ -17,11 +20,13 @@ import com.google.common.util.concurrent.RateLimiter; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.renderer.Rect2i; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Inventory; import org.joml.Vector3f; @@ -38,10 +43,32 @@ public class AutocraftingPreviewScreen extends AbstractAmountScreen requestButtons = new ArrayList<>(); private final boolean requestsButtonsVisible; private final RateLimiter requestRateLimiter = RateLimiter.create(1); + private final RateLimiter maxAmountRequestRateLimiter = RateLimiter.create(1 / 5D); @Nullable private Double changedAmount; + private boolean mayEnableMaxAmountRequestButtonAgain; public AutocraftingPreviewScreen(final Screen parent, final Inventory playerInventory, @@ -114,6 +145,7 @@ public AutocraftingPreviewScreen(final AutocraftingPreviewContainerMenu menu, @Override protected void init() { + final boolean wasAlreadyInitialized = amountField != null; super.init(); previewItemsScrollbar = new ScrollbarWidget( leftPos + 235, @@ -128,7 +160,18 @@ protected void init() { if (confirmButton != null) { setStartDisabled(); } - getMenu().loadCurrentRequest(); + if (!wasAlreadyInitialized) { + getMenu().loadCurrentRequest(); + } + + final boolean wasActive = maxButton == null || maxButton.active; + maxButton = Button.builder(MAX, this::requestMaxAmount) + .size(22, 15) + .pos(leftPos + 185 - 1, topPos + 49 - 1) + .build(); + maxButton.active = wasActive; + addRenderableWidget(maxButton); + getExclusionZones().add(new Rect2i( leftPos - REQUESTS_WIDTH + 4, topPos, @@ -137,6 +180,11 @@ protected void init() { )); } + private void requestMaxAmount(final Button button) { + button.active = false; + getMenu().requestMaxAmount(); + } + private void initRequestButtons() { requestButtons.clear(); requestButtonsScrollbar = new ScrollbarWidget( @@ -254,16 +302,110 @@ protected void renderBg(final GuiGraphics graphics, final float delta, final int final int x = leftPos + 8; final int y = topPos + 98; graphics.enableScissor(x, y, x + 221, y + PREVIEW_AREA_HEIGHT); + if (preview.type() == PreviewType.CYCLE_DETECTED) { + renderCycleDetected(graphics, y, x, preview); + } else if (preview.type() == PreviewType.OVERFLOW) { + renderRequestTooLargeToHandle(graphics, x, y); + } else { + renderPreviewRows(graphics, mouseX, mouseY, preview, y, x); + } + graphics.disableScissor(); + } + + private void renderCycleDetected(final GuiGraphics graphics, final int y, final int x, final Preview preview) { + int yy = y + 4; + SmallText.render( + graphics, + font, + CYCLE_DETECTED.getVisualOrderText(), + x + 4, + yy, + 0xFF5555, + false + ); + yy += 10; + SmallText.render( + graphics, + font, + CYCLE_OUTPUTS.getVisualOrderText(), + x + 4, + yy, + 0x404040, + false + ); + yy += 10; + for (final ResourceAmount output : preview.outputsOfPatternWithCycle()) { + final ResourceRendering rendering = RefinedStorageClientApi.INSTANCE.getResourceRendering( + output.resource().getClass() + ); + rendering.render(output.resource(), graphics, x + 4, yy); + SmallText.render( + graphics, + font, + Component.literal(output.amount() + "x ").append(rendering.getDisplayName(output.resource())) + .getVisualOrderText(), + x + 4 + 16 + 3, + yy + 5, + 0x404040, + false + ); + yy += 18; + } + yy += 2; + SmallText.render( + graphics, + font, + BREAK_THE_CYCLE_AND_TRY_AGAIN.getVisualOrderText(), + x + 4, + yy, + 0x404040, + false + ); + } + + private void renderRequestTooLargeToHandle(final GuiGraphics graphics, final int x, final int y) { + SmallText.render( + graphics, + font, + REQUEST_TOO_LARGE_TO_HANDLE.getVisualOrderText(), + x + 4, + y + 4, + 0xFF5555, + false + ); + SmallText.render( + graphics, + font, + TRY_SMALLER_AMOUNT.getVisualOrderText(), + x + 4, + y + 4 + 10, + 0x404040, + false + ); + } + + private void renderPreviewRows(final GuiGraphics graphics, + final int mouseX, + final int mouseY, + final Preview preview, + final int y, + final int x) { final List items = preview.items(); final int rows = Math.ceilDiv(items.size(), COLUMNS); for (int i = 0; i < rows; ++i) { - final int scrollOffset = previewItemsScrollbar.isSmoothScrolling() - ? (int) previewItemsScrollbar.getOffset() - : (int) previewItemsScrollbar.getOffset() * ROW_HEIGHT; + final int scrollOffset = getScrollOffset(); final int yy = y + (i * ROW_HEIGHT) - scrollOffset; renderRow(graphics, x, yy, i, items, mouseX, mouseY); } - graphics.disableScissor(); + } + + private int getScrollOffset() { + if (previewItemsScrollbar == null) { + return 0; + } + return (previewItemsScrollbar.isSmoothScrolling() + ? (int) previewItemsScrollbar.getOffset() + : (int) previewItemsScrollbar.getOffset() * ROW_HEIGHT); } private void renderRow(final GuiGraphics graphics, @@ -340,6 +482,19 @@ private void renderCellText(final GuiGraphics graphics, ); } + @Override + protected void renderTooltip(final GuiGraphics graphics, final int x, final int y) { + super.renderTooltip(graphics, x, y); + if (maxButton != null && maxButton.isHovered()) { + Platform.INSTANCE.renderTooltip( + graphics, + List.of(HelpClientTooltipComponent.createAlwaysDisplayed(MAX_HELP)), + x, + y + ); + } + } + @Override public boolean mouseClicked(final double mouseX, final double mouseY, final int clickedButton) { if (previewItemsScrollbar != null && previewItemsScrollbar.mouseClicked(mouseX, mouseY, clickedButton)) { @@ -424,6 +579,9 @@ protected void onAmountFieldChanged() { } private void setPending() { + if (confirmButton == null) { + return; + } confirmButton.active = false; confirmButton.setError(false); confirmButton.setTooltip(null); @@ -431,6 +589,9 @@ private void setPending() { } private void setStartDisabled() { + if (confirmButton == null) { + return; + } confirmButton.active = false; confirmButton.setError(false); confirmButton.setTooltip(null); @@ -444,6 +605,10 @@ protected void containerTick() { getMenu().amountChanged(changedAmount); changedAmount = null; } + if (mayEnableMaxAmountRequestButtonAgain && maxButton != null && maxAmountRequestRateLimiter.tryAcquire()) { + maxButton.active = true; + mayEnableMaxAmountRequestButtonAgain = false; + } } @Override @@ -487,6 +652,13 @@ public void requestRemoved(final AutocraftingRequest request, final boolean last } } + @Override + public void maxAmountReceived(final double maxAmount) { + updateAmount(maxAmount); + maxAmountRequestRateLimiter.tryAcquire(); + mayEnableMaxAmountRequestButtonAgain = true; + } + private void updateRequestsScrollbar() { if (requestButtonsScrollbar == null) { return; diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/AbstractGridBlockEntity.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/AbstractGridBlockEntity.java index bb1180725..17997f8e0 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/AbstractGridBlockEntity.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/AbstractGridBlockEntity.java @@ -107,6 +107,14 @@ public Optional getPreview(final ResourceKey resource, final long amoun .flatMap(component -> component.getPreview(resource, amount)); } + @Override + public long getMaxAmount(final ResourceKey resource) { + return Optional.ofNullable(mainNetworkNode.getNetwork()) + .map(network -> network.getComponent(AutocraftingNetworkComponent.class)) + .map(component -> component.getMaxAmount(resource)) + .orElse(0L); + } + @Override public boolean startTask(final ResourceKey resource, final long amount) { final Network network = mainNetworkNode.getNetwork(); diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/AbstractGridContainerMenu.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/AbstractGridContainerMenu.java index 64ca79739..ebde4fd05 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/AbstractGridContainerMenu.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/AbstractGridContainerMenu.java @@ -463,6 +463,11 @@ public Optional getPreview(final ResourceKey resource, final long amoun return requireNonNull(grid).getPreview(resource, amount); } + @Override + public long getMaxAmount(final ResourceKey resource) { + return requireNonNull(grid).getMaxAmount(resource); + } + @Override public boolean startTask(final ResourceKey resource, final long amount) { return requireNonNull(grid).startTask(resource, amount); diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/ClientCraftingGrid.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/ClientCraftingGrid.java index d4510fbe0..c4de47773 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/ClientCraftingGrid.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/ClientCraftingGrid.java @@ -109,6 +109,11 @@ public Optional getPreview(final ResourceKey resource, final long amoun throw new UnsupportedOperationException(); } + @Override + public long getMaxAmount(final ResourceKey resource) { + return 0; + } + @Override public boolean startTask(final ResourceKey resource, final long amount) { throw new UnsupportedOperationException(); diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/WirelessGrid.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/WirelessGrid.java index d48a18765..88c5d046e 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/WirelessGrid.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/grid/WirelessGrid.java @@ -120,6 +120,11 @@ public Optional getPreview(final ResourceKey resource, final long amoun return getAutocrafting().flatMap(component -> component.getPreview(resource, amount)); } + @Override + public long getMaxAmount(final ResourceKey resource) { + return getAutocrafting().map(component -> component.getMaxAmount(resource)).orElse(0L); + } + @Override public boolean startTask(final ResourceKey resource, final long amount) { return getAutocrafting().map(autocrafting -> autocrafting.startTask(resource, amount)).orElse(false); diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/storage/portablegrid/PortableGrid.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/storage/portablegrid/PortableGrid.java index 87df2e7ae..6468b9fdb 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/storage/portablegrid/PortableGrid.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/storage/portablegrid/PortableGrid.java @@ -135,6 +135,11 @@ public Optional getPreview(final ResourceKey resource, final long amoun return Optional.empty(); } + @Override + public long getMaxAmount(final ResourceKey resource) { + return 0; + } + @Override public boolean startTask(final ResourceKey resource, final long amount) { return false; diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/storagemonitor/AutocraftingStorageMonitorContainerMenu.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/storagemonitor/AutocraftingStorageMonitorContainerMenu.java index edee1c301..d7d548d63 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/storagemonitor/AutocraftingStorageMonitorContainerMenu.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/storagemonitor/AutocraftingStorageMonitorContainerMenu.java @@ -43,6 +43,11 @@ public Optional getPreview(final ResourceKey resource, final long amoun return requireNonNull(storageMonitor).getPreview(resource, amount); } + @Override + public long getMaxAmount(final ResourceKey resource) { + return requireNonNull(storageMonitor).getMaxAmount(resource); + } + @Override public boolean startTask(final ResourceKey resource, final long amount) { return requireNonNull(storageMonitor).startTask(resource, amount); diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/storagemonitor/StorageMonitorBlockEntity.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/storagemonitor/StorageMonitorBlockEntity.java index d3b078ab7..b11782669 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/storagemonitor/StorageMonitorBlockEntity.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/storagemonitor/StorageMonitorBlockEntity.java @@ -373,6 +373,14 @@ public Optional getPreview(final ResourceKey resource, final long amoun .flatMap(component -> component.getPreview(resource, amount)); } + @Override + public long getMaxAmount(final ResourceKey resource) { + return Optional.ofNullable(mainNetworkNode.getNetwork()) + .map(network -> network.getComponent(AutocraftingNetworkComponent.class)) + .map(component -> component.getMaxAmount(resource)) + .orElse(0L); + } + @Override public boolean startTask(final ResourceKey resource, final long amount) { final Network network = mainNetworkNode.getNetwork(); diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AbstractAmountScreen.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AbstractAmountScreen.java index 66b5c5e07..4a8baab44 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AbstractAmountScreen.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/amount/AbstractAmountScreen.java @@ -117,6 +117,7 @@ private Button addCancelButton(final int x, final int y) { private void addAmountField() { final Vector3f pos = configuration.getAmountFieldPosition(); + final String originalValue = amountField != null ? amountField.getValue() : null; amountField = new EditBox( font, leftPos + (int) pos.x(), @@ -126,14 +127,17 @@ private void addAmountField() { Component.empty() ); amountField.setBordered(false); - if (configuration.getInitialAmount() != null) { + amountField.setTextColor(0xFFFFFF); + if (originalValue != null) { + amountField.setValue(originalValue); + onAmountFieldChanged(); + } else if (configuration.getInitialAmount() != null) { updateAmount(configuration.getInitialAmount()); } amountField.setVisible(true); amountField.setCanLoseFocus(this instanceof AlternativesScreen); amountField.setFocused(true); amountField.setResponder(value -> onAmountFieldChanged()); - amountField.setTextColor(0xFFFFFF); setFocused(amountField); addRenderableWidget(amountField); diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/c2s/AutocraftingPreviewMaxAmountRequestPacket.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/c2s/AutocraftingPreviewMaxAmountRequestPacket.java new file mode 100644 index 000000000..9856a36a9 --- /dev/null +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/c2s/AutocraftingPreviewMaxAmountRequestPacket.java @@ -0,0 +1,38 @@ +package com.refinedmods.refinedstorage.common.support.packet.c2s; + +import com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewProvider; +import com.refinedmods.refinedstorage.common.api.support.resource.PlatformResourceKey; +import com.refinedmods.refinedstorage.common.support.packet.PacketContext; +import com.refinedmods.refinedstorage.common.support.packet.s2c.S2CPackets; +import com.refinedmods.refinedstorage.common.support.resource.ResourceCodecs; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.server.level.ServerPlayer; + +import static com.refinedmods.refinedstorage.common.util.IdentifierUtil.createIdentifier; + +public record AutocraftingPreviewMaxAmountRequestPacket(PlatformResourceKey resource) implements CustomPacketPayload { + public static final Type PACKET_TYPE = new Type<>(createIdentifier( + "autocrafting_preview_max_amount_request" + )); + public static final StreamCodec STREAM_CODEC = + StreamCodec.composite( + ResourceCodecs.STREAM_CODEC, AutocraftingPreviewMaxAmountRequestPacket::resource, + AutocraftingPreviewMaxAmountRequestPacket::new + ); + + public static void handle(final AutocraftingPreviewMaxAmountRequestPacket packet, final PacketContext ctx) { + if (ctx.getPlayer().containerMenu instanceof PreviewProvider provider) { + final ServerPlayer player = (ServerPlayer) ctx.getPlayer(); + final long maxAmount = provider.getMaxAmount(packet.resource); + S2CPackets.sendAutocraftingPreviewMaxAmountResponse(player, maxAmount); + } + } + + @Override + public Type type() { + return PACKET_TYPE; + } +} diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/c2s/C2SPackets.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/c2s/C2SPackets.java index 96bb861b9..1c4fd5f6f 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/c2s/C2SPackets.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/c2s/C2SPackets.java @@ -145,6 +145,10 @@ public static void sendAutocraftingRequest(final UUID id, Platform.INSTANCE.sendPacketToServer(new AutocraftingRequestPacket(id, resource, amount)); } + public static void sendAutocraftingPreviewMaxAmountRequest(final PlatformResourceKey resource) { + Platform.INSTANCE.sendPacketToServer(new AutocraftingPreviewMaxAmountRequestPacket(resource)); + } + public static void sendAutocraftingMonitorCancel(final TaskId taskId) { Platform.INSTANCE.sendPacketToServer(new AutocraftingMonitorCancelPacket(taskId)); } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/s2c/AutocraftingPreviewMaxAmountResponsePacket.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/s2c/AutocraftingPreviewMaxAmountResponsePacket.java new file mode 100644 index 000000000..093f822e8 --- /dev/null +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/s2c/AutocraftingPreviewMaxAmountResponsePacket.java @@ -0,0 +1,31 @@ +package com.refinedmods.refinedstorage.common.support.packet.s2c; + +import com.refinedmods.refinedstorage.common.util.ClientPlatformUtil; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +import static com.refinedmods.refinedstorage.common.util.IdentifierUtil.createIdentifier; + +public record AutocraftingPreviewMaxAmountResponsePacket(long maxAmount) implements CustomPacketPayload { + public static final Type PACKET_TYPE = new Type<>( + createIdentifier("autocrafting_preview_max_amount_response") + ); + public static final StreamCodec STREAM_CODEC = + StreamCodec.composite( + ByteBufCodecs.VAR_LONG, AutocraftingPreviewMaxAmountResponsePacket::maxAmount, + AutocraftingPreviewMaxAmountResponsePacket::new + ); + + public static void handle(final AutocraftingPreviewMaxAmountResponsePacket packet) { + ClientPlatformUtil.autocraftingPreviewMaxAmountResponseReceived(packet.maxAmount); + } + + @Override + public Type type() { + return PACKET_TYPE; + } +} + diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/s2c/AutocraftingPreviewResponsePacket.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/s2c/AutocraftingPreviewResponsePacket.java index d46a880d1..297420f7f 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/s2c/AutocraftingPreviewResponsePacket.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/s2c/AutocraftingPreviewResponsePacket.java @@ -35,6 +35,8 @@ public record AutocraftingPreviewResponsePacket(UUID id, Preview preview) implem StreamCodec.composite( enumStreamCodec(PreviewType.values()), Preview::type, ByteBufCodecs.collection(ArrayList::new, PREVIEW_ITEM_STREAM_CODEC), Preview::items, + ByteBufCodecs.collection(ArrayList::new, ResourceCodecs.AMOUNT_STREAM_CODEC), + Preview::outputsOfPatternWithCycle, Preview::new ); public static final StreamCodec STREAM_CODEC = diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/s2c/S2CPackets.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/s2c/S2CPackets.java index d114b3a9c..d36b65168 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/s2c/S2CPackets.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/support/packet/s2c/S2CPackets.java @@ -99,6 +99,10 @@ public static void sendAutocraftingPreviewResponse(final ServerPlayer player, Platform.INSTANCE.sendPacketToClient(player, new AutocraftingPreviewResponsePacket(id, preview)); } + public static void sendAutocraftingPreviewMaxAmountResponse(final ServerPlayer player, final long maxAmount) { + Platform.INSTANCE.sendPacketToClient(player, new AutocraftingPreviewMaxAmountResponsePacket(maxAmount)); + } + public static void sendAutocraftingResponse(final ServerPlayer player, final UUID id, final boolean started) { diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/util/ClientPlatformUtil.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/util/ClientPlatformUtil.java index 12b37c339..b47b15fe0 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/util/ClientPlatformUtil.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/util/ClientPlatformUtil.java @@ -52,6 +52,12 @@ public static void autocraftingResponseReceived(final UUID id, final boolean sta } } + public static void autocraftingPreviewMaxAmountResponseReceived(final long maxAmount) { + if (Minecraft.getInstance().screen instanceof AutocraftingPreviewScreen screen) { + screen.getMenu().maxAmountResponseReceived(maxAmount); + } + } + public static void openCraftingPreview(final List requests, @Nullable final Object parentScreen) { final Minecraft minecraft = Minecraft.getInstance(); if ((!(parentScreen instanceof Screen) && minecraft.screen == null) || minecraft.player == null) { diff --git a/refinedstorage-common/src/main/resources/assets/refinedstorage/lang/en_us.json b/refinedstorage-common/src/main/resources/assets/refinedstorage/lang/en_us.json index 5ec7d5adc..38d0785eb 100644 --- a/refinedstorage-common/src/main/resources/assets/refinedstorage/lang/en_us.json +++ b/refinedstorage-common/src/main/resources/assets/refinedstorage/lang/en_us.json @@ -228,6 +228,13 @@ "gui.refinedstorage.autocrafting_preview.available": "Available: %s", "gui.refinedstorage.autocrafting_preview.to_craft": "To craft: %s", "gui.refinedstorage.autocrafting_preview.missing": "Missing: %s", + "gui.refinedstorage.autocrafting_preview.max": "Max", + "gui.refinedstorage.autocrafting_preview.max.help": "Fills in the maximum number of this resource you can craft right now without starting the crafting process yet.", + "gui.refinedstorage.autocrafting_preview.cycle_detected": "A pattern cycle was detected!", + "gui.refinedstorage.autocrafting_preview.cycle_detected.outputs": "The pattern had following outputs:", + "gui.refinedstorage.autocrafting_preview.cycle_detected.break_the_cycle_and_try_again": "Break the cycle and try again.", + "gui.refinedstorage.autocrafting_preview.request_too_large_to_handle": "The request is too large to handle!", + "gui.refinedstorage.autocrafting_preview.request_too_large_to_handle.try_smaller_amount": "Try a smaller amount.", "gui.refinedstorage.autocrafter_manager.search_mode": "Search mode", "gui.refinedstorage.autocrafter_manager.search_mode.all": "All", "gui.refinedstorage.autocrafter_manager.search_mode.all.help": "Search in pattern inputs, pattern outputs and autocrafter names.", diff --git a/refinedstorage-fabric/src/main/java/com/refinedmods/refinedstorage/fabric/ClientModInitializerImpl.java b/refinedstorage-fabric/src/main/java/com/refinedmods/refinedstorage/fabric/ClientModInitializerImpl.java index 27d301f9a..5c2fc9147 100644 --- a/refinedstorage-fabric/src/main/java/com/refinedmods/refinedstorage/fabric/ClientModInitializerImpl.java +++ b/refinedstorage-fabric/src/main/java/com/refinedmods/refinedstorage/fabric/ClientModInitializerImpl.java @@ -25,6 +25,7 @@ import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingMonitorTaskAddedPacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingMonitorTaskRemovedPacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingMonitorTaskStatusChangedPacket; +import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingPreviewMaxAmountResponsePacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingPreviewResponsePacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingResponsePacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.EnergyInfoPacket; @@ -311,6 +312,10 @@ private void registerPacketHandlers() { AutocraftingPreviewResponsePacket.PACKET_TYPE, wrapHandler((packet, ctx) -> AutocraftingPreviewResponsePacket.handle(packet)) ); + ClientPlayNetworking.registerGlobalReceiver( + AutocraftingPreviewMaxAmountResponsePacket.PACKET_TYPE, + wrapHandler((packet, ctx) -> AutocraftingPreviewMaxAmountResponsePacket.handle(packet)) + ); ClientPlayNetworking.registerGlobalReceiver( AutocraftingResponsePacket.PACKET_TYPE, wrapHandler((packet, ctx) -> AutocraftingResponsePacket.handle(packet)) diff --git a/refinedstorage-fabric/src/main/java/com/refinedmods/refinedstorage/fabric/ModInitializerImpl.java b/refinedstorage-fabric/src/main/java/com/refinedmods/refinedstorage/fabric/ModInitializerImpl.java index 731711db6..df1bb4ebf 100644 --- a/refinedstorage-fabric/src/main/java/com/refinedmods/refinedstorage/fabric/ModInitializerImpl.java +++ b/refinedstorage-fabric/src/main/java/com/refinedmods/refinedstorage/fabric/ModInitializerImpl.java @@ -33,6 +33,7 @@ import com.refinedmods.refinedstorage.common.support.packet.c2s.AutocrafterNameChangePacket; import com.refinedmods.refinedstorage.common.support.packet.c2s.AutocraftingMonitorCancelAllPacket; import com.refinedmods.refinedstorage.common.support.packet.c2s.AutocraftingMonitorCancelPacket; +import com.refinedmods.refinedstorage.common.support.packet.c2s.AutocraftingPreviewMaxAmountRequestPacket; import com.refinedmods.refinedstorage.common.support.packet.c2s.AutocraftingPreviewRequestPacket; import com.refinedmods.refinedstorage.common.support.packet.c2s.AutocraftingRequestPacket; import com.refinedmods.refinedstorage.common.support.packet.c2s.CraftingGridClearPacket; @@ -64,6 +65,7 @@ import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingMonitorTaskAddedPacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingMonitorTaskRemovedPacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingMonitorTaskStatusChangedPacket; +import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingPreviewMaxAmountResponsePacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingPreviewResponsePacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingResponsePacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.EnergyInfoPacket; @@ -491,6 +493,10 @@ private void registerServerToClientPackets() { AutocraftingPreviewResponsePacket.PACKET_TYPE, AutocraftingPreviewResponsePacket.STREAM_CODEC ); + PayloadTypeRegistry.playS2C().register( + AutocraftingPreviewMaxAmountResponsePacket.PACKET_TYPE, + AutocraftingPreviewMaxAmountResponsePacket.STREAM_CODEC + ); PayloadTypeRegistry.playS2C().register( AutocraftingResponsePacket.PACKET_TYPE, AutocraftingResponsePacket.STREAM_CODEC @@ -602,6 +608,10 @@ private void registerClientToServerPackets() { AutocraftingPreviewRequestPacket.PACKET_TYPE, AutocraftingPreviewRequestPacket.STREAM_CODEC ); + PayloadTypeRegistry.playC2S().register( + AutocraftingPreviewMaxAmountRequestPacket.PACKET_TYPE, + AutocraftingPreviewMaxAmountRequestPacket.STREAM_CODEC + ); PayloadTypeRegistry.playC2S().register( AutocraftingRequestPacket.PACKET_TYPE, AutocraftingRequestPacket.STREAM_CODEC @@ -717,6 +727,10 @@ private void registerPacketHandlers() { AutocraftingPreviewRequestPacket.PACKET_TYPE, wrapHandler(AutocraftingPreviewRequestPacket::handle) ); + ServerPlayNetworking.registerGlobalReceiver( + AutocraftingPreviewMaxAmountRequestPacket.PACKET_TYPE, + wrapHandler(AutocraftingPreviewMaxAmountRequestPacket::handle) + ); ServerPlayNetworking.registerGlobalReceiver( AutocraftingRequestPacket.PACKET_TYPE, wrapHandler(AutocraftingRequestPacket::handle) diff --git a/refinedstorage-neoforge/src/main/java/com/refinedmods/refinedstorage/neoforge/ModInitializer.java b/refinedstorage-neoforge/src/main/java/com/refinedmods/refinedstorage/neoforge/ModInitializer.java index 8a823eed4..3d2ee4ca3 100644 --- a/refinedstorage-neoforge/src/main/java/com/refinedmods/refinedstorage/neoforge/ModInitializer.java +++ b/refinedstorage-neoforge/src/main/java/com/refinedmods/refinedstorage/neoforge/ModInitializer.java @@ -34,6 +34,7 @@ import com.refinedmods.refinedstorage.common.support.packet.c2s.AutocrafterNameChangePacket; import com.refinedmods.refinedstorage.common.support.packet.c2s.AutocraftingMonitorCancelAllPacket; import com.refinedmods.refinedstorage.common.support.packet.c2s.AutocraftingMonitorCancelPacket; +import com.refinedmods.refinedstorage.common.support.packet.c2s.AutocraftingPreviewMaxAmountRequestPacket; import com.refinedmods.refinedstorage.common.support.packet.c2s.AutocraftingPreviewRequestPacket; import com.refinedmods.refinedstorage.common.support.packet.c2s.AutocraftingRequestPacket; import com.refinedmods.refinedstorage.common.support.packet.c2s.CraftingGridClearPacket; @@ -65,6 +66,7 @@ import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingMonitorTaskAddedPacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingMonitorTaskRemovedPacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingMonitorTaskStatusChangedPacket; +import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingPreviewMaxAmountResponsePacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingPreviewResponsePacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.AutocraftingResponsePacket; import com.refinedmods.refinedstorage.common.support.packet.s2c.EnergyInfoPacket; @@ -688,6 +690,11 @@ private static void registerServerToClientPackets(final PayloadRegistrar registr AutocraftingPreviewResponsePacket.STREAM_CODEC, wrapHandler((packet, ctx) -> AutocraftingPreviewResponsePacket.handle(packet)) ); + registrar.playToClient( + AutocraftingPreviewMaxAmountResponsePacket.PACKET_TYPE, + AutocraftingPreviewMaxAmountResponsePacket.STREAM_CODEC, + wrapHandler((packet, ctx) -> AutocraftingPreviewMaxAmountResponsePacket.handle(packet)) + ); registrar.playToClient( AutocraftingResponsePacket.PACKET_TYPE, AutocraftingResponsePacket.STREAM_CODEC, @@ -841,6 +848,11 @@ private static void registerClientToServerPackets(final PayloadRegistrar registr AutocraftingPreviewRequestPacket.STREAM_CODEC, wrapHandler(AutocraftingPreviewRequestPacket::handle) ); + registrar.playToServer( + AutocraftingPreviewMaxAmountRequestPacket.PACKET_TYPE, + AutocraftingPreviewMaxAmountRequestPacket.STREAM_CODEC, + wrapHandler(AutocraftingPreviewMaxAmountRequestPacket::handle) + ); registrar.playToServer( AutocraftingRequestPacket.PACKET_TYPE, AutocraftingRequestPacket.STREAM_CODEC, diff --git a/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImpl.java b/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImpl.java index 2aba9c211..2a7865e2d 100644 --- a/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImpl.java +++ b/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImpl.java @@ -79,9 +79,14 @@ public boolean contains(final AutocraftingNetworkComponent component) { public Optional getPreview(final ResourceKey resource, final long amount) { final RootStorage rootStorage = rootStorageProvider.get(); final CraftingCalculator craftingCalculator = new CraftingCalculatorImpl(patternRepository, rootStorage); - final PreviewCraftingCalculatorListener listener = PreviewCraftingCalculatorListener.ofRoot(); - craftingCalculator.calculate(resource, amount, listener); - return Optional.of(listener.buildPreview()); + return Optional.of(PreviewCraftingCalculatorListener.calculatePreview(craftingCalculator, resource, amount)); + } + + @Override + public long getMaxAmount(final ResourceKey resource) { + final RootStorage rootStorage = rootStorageProvider.get(); + final CraftingCalculator craftingCalculator = new CraftingCalculatorImpl(patternRepository, rootStorage); + return craftingCalculator.getMaxAmount(resource); } @Override diff --git a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImplTest.java b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImplTest.java index 002603538..c4edd7920 100644 --- a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImplTest.java +++ b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImplTest.java @@ -19,6 +19,7 @@ import com.refinedmods.refinedstorage.api.storage.root.RootStorageImpl; import com.refinedmods.refinedstorage.network.test.fixtures.FakeTaskStatusProvider; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -166,6 +167,6 @@ void shouldGetPreview() { .isEqualTo(new Preview(PreviewType.SUCCESS, List.of( new PreviewItem(B, 0, 0, 2), new PreviewItem(A, 6, 0, 0) - ))); + ), Collections.emptyList())); } }