diff --git a/CHANGELOG.md b/CHANGELOG.md index f79de1e53..3fa590d4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + +- Autocrafting engine. + +### 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. + ## [2.0.0-milestone.4.11] - 2024-12-08 ### Added diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/Ingredient.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/Ingredient.java new file mode 100644 index 000000000..88c8c68fb --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/Ingredient.java @@ -0,0 +1,40 @@ +package com.refinedmods.refinedstorage.api.autocrafting; + +import com.refinedmods.refinedstorage.api.resource.ResourceKey; + +import java.util.Collections; +import java.util.List; + +public class Ingredient { + private final long amount; + private final List inputs; + + public Ingredient(final long amount, final List inputs) { + this.amount = amount; + this.inputs = Collections.unmodifiableList(inputs); + } + + public boolean isEmpty() { + return inputs.isEmpty(); + } + + public int size() { + return inputs.size(); + } + + public long getAmount() { + return amount; + } + + public ResourceKey get(final int index) { + return inputs.get(index); + } + + @Override + public String toString() { + return "Ingredient{" + + "amount=" + amount + + ", inputs=" + inputs + + '}'; + } +} diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/Pattern.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/Pattern.java index ce80eeaa6..ec74da532 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/Pattern.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/Pattern.java @@ -1,7 +1,9 @@ package com.refinedmods.refinedstorage.api.autocrafting; +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; import com.refinedmods.refinedstorage.api.resource.ResourceKey; +import java.util.List; import java.util.Set; import org.apiguardian.api.API; @@ -11,4 +13,8 @@ public interface Pattern { Set getInputResources(); Set getOutputResources(); + + List getIngredients(); + + List getOutputs(); } diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/PatternRepository.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/PatternRepository.java index 9d6d174fa..b1f5c44b1 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/PatternRepository.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/PatternRepository.java @@ -2,6 +2,7 @@ import com.refinedmods.refinedstorage.api.resource.ResourceKey; +import java.util.List; import java.util.Set; import org.apiguardian.api.API; @@ -12,6 +13,8 @@ public interface PatternRepository { void remove(Pattern pattern); + List getByOutput(ResourceKey output); + Set getOutputs(); Set getAll(); diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/PatternRepositoryImpl.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/PatternRepositoryImpl.java index 4f8139fc2..acbcfe47b 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/PatternRepositoryImpl.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/PatternRepositoryImpl.java @@ -2,12 +2,17 @@ import com.refinedmods.refinedstorage.api.resource.ResourceKey; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; public class PatternRepositoryImpl implements PatternRepository { private final Set patterns = new HashSet<>(); + private final Map> patternsByOutput = new HashMap<>(); private final Set patternsView = Collections.unmodifiableSet(patterns); private final Set outputs = new HashSet<>(); @@ -15,12 +20,23 @@ public class PatternRepositoryImpl implements PatternRepository { public void add(final Pattern pattern) { patterns.add(pattern); outputs.addAll(pattern.getOutputResources()); + for (final ResourceKey output : pattern.getOutputResources()) { + patternsByOutput.computeIfAbsent(output, k -> new ArrayList<>()).add(pattern); + } } @Override public void remove(final Pattern pattern) { patterns.remove(pattern); for (final ResourceKey output : pattern.getOutputResources()) { + final List byOutput = patternsByOutput.get(output); + if (byOutput == null) { + continue; + } + byOutput.remove(pattern); + if (byOutput.isEmpty()) { + patternsByOutput.remove(output); + } final boolean noOtherPatternHasThisOutput = patterns.stream() .noneMatch(otherPattern -> otherPattern.getOutputResources().contains(output)); if (noOtherPatternHasThisOutput) { @@ -29,6 +45,11 @@ public void remove(final Pattern pattern) { } } + @Override + public List getByOutput(final ResourceKey output) { + return patternsByOutput.getOrDefault(output, Collections.emptyList()); + } + @Override public Set getOutputs() { return outputs; diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/Amount.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/Amount.java new file mode 100644 index 000000000..1522a35b6 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/Amount.java @@ -0,0 +1,21 @@ +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; + +record Amount(long iterations, long amountPerIteration) { + public long getTotal() { + return iterations * amountPerIteration; + } + + static Amount of(final Pattern pattern, final ResourceKey resource, final long requestedAmount) { + final long amountPerIteration = pattern.getOutputs() + .stream() + .filter(output -> output.resource().equals(resource)) + .mapToLong(ResourceAmount::amount) + .sum(); + final long iterations = ((requestedAmount - 1) / amountPerIteration) + 1; + return new Amount(iterations, amountPerIteration); + } +} 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 new file mode 100644 index 000000000..ec3f665ef --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculator.java @@ -0,0 +1,11 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +import com.refinedmods.refinedstorage.api.resource.ResourceKey; + +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); +} 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 new file mode 100644 index 000000000..4661bb873 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculatorImpl.java @@ -0,0 +1,48 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +import com.refinedmods.refinedstorage.api.autocrafting.Pattern; +import com.refinedmods.refinedstorage.api.autocrafting.PatternRepository; +import com.refinedmods.refinedstorage.api.core.CoreValidations; +import com.refinedmods.refinedstorage.api.resource.ResourceKey; +import com.refinedmods.refinedstorage.api.storage.root.RootStorage; + +import java.util.List; + +import static com.refinedmods.refinedstorage.api.autocrafting.calculation.CraftingTree.root; + +public class CraftingCalculatorImpl implements CraftingCalculator { + private final PatternRepository patternRepository; + private final RootStorage rootStorage; + + public CraftingCalculatorImpl(final PatternRepository patternRepository, final RootStorage rootStorage) { + this.patternRepository = patternRepository; + this.rootStorage = rootStorage; + } + + @Override + public void calculate(final ResourceKey resource, + final long amount, + final CraftingCalculatorListener listener) { + CoreValidations.validateLargerThanZero(amount, "Requested amount must be greater than 0"); + final List patterns = patternRepository.getByOutput(resource); + CraftingCalculatorListener lastChildListener = null; + Amount lastPatternAmount = null; + for (final Pattern pattern : patterns) { + final Amount patternAmount = Amount.of(pattern, resource, amount); + final CraftingCalculatorListener childListener = listener.childCalculationStarted(); + final CraftingTree tree = root(pattern, rootStorage, patternAmount, patternRepository, childListener); + final CraftingTree.CalculationResult calculationResult = tree.calculate(); + if (calculationResult == CraftingTree.CalculationResult.MISSING_RESOURCES) { + lastChildListener = childListener; + lastPatternAmount = patternAmount; + continue; + } + listener.childCalculationCompleted(resource, patternAmount.getTotal(), childListener); + return; + } + if (lastChildListener == null) { + throw new IllegalStateException("No pattern found for " + resource); + } + listener.childCalculationCompleted(resource, lastPatternAmount.getTotal(), lastChildListener); + } +} diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculatorListener.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculatorListener.java new file mode 100644 index 000000000..0ca6675e9 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculatorListener.java @@ -0,0 +1,16 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +import com.refinedmods.refinedstorage.api.resource.ResourceKey; + +import org.apiguardian.api.API; + +@API(status = API.Status.STABLE, since = "2.0.0-milestone.4.12") +public interface CraftingCalculatorListener { + CraftingCalculatorListener childCalculationStarted(); + + void childCalculationCompleted(ResourceKey resource, long amount, CraftingCalculatorListener childListener); + + void ingredientsExhausted(ResourceKey resource, long amount); + + void ingredientExtractedFromStorage(ResourceKey resource, long amount); +} 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 new file mode 100644 index 000000000..6e287893b --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingState.java @@ -0,0 +1,74 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +import com.refinedmods.refinedstorage.api.autocrafting.Pattern; +import com.refinedmods.refinedstorage.api.resource.ResourceKey; +import com.refinedmods.refinedstorage.api.resource.list.MutableResourceList; +import com.refinedmods.refinedstorage.api.resource.list.MutableResourceListImpl; +import com.refinedmods.refinedstorage.api.storage.root.RootStorage; + +import java.util.Comparator; + +class CraftingState { + private final MutableResourceList storage; + private final MutableResourceList internalStorage; + + private CraftingState(final MutableResourceList storage, + final MutableResourceList internalStorage) { + this.storage = storage; + this.internalStorage = internalStorage; + } + + void extractFromInternalStorage(final ResourceKey resource, final long amount) { + internalStorage.remove(resource, amount); + } + + void extractFromStorage(final ResourceKey resource, final long amount) { + storage.remove(resource, amount); + } + + void addOutputsToInternalStorage(final Pattern pattern, final Amount amount) { + pattern.getOutputs().forEach( + output -> internalStorage.add(output.resource(), output.amount() * amount.iterations()) + ); + } + + CraftingState copy() { + return new CraftingState(storage.copy(), internalStorage.copy()); + } + + static CraftingState of(final RootStorage rootStorage) { + final MutableResourceListImpl storage = MutableResourceListImpl.create(); + rootStorage.getAll().forEach(storage::add); + return new CraftingState(storage, MutableResourceListImpl.create()); + } + + Comparator storageSorter() { + return sorter(storage); + } + + Comparator internalStorageSorter() { + return sorter(internalStorage); + } + + private Comparator sorter(final MutableResourceList list) { + return (a, b) -> { + final long ar = list.get(a); + final long br = list.get(b); + return (int) br - (int) ar; + }; + } + + ResourceState getResource(final ResourceKey resource) { + return new ResourceState(resource, storage.get(resource), internalStorage.get(resource)); + } + + record ResourceState(ResourceKey resource, long inStorage, long inInternalStorage) { + boolean isInStorage() { + return inStorage > 0; + } + + boolean isInInternalStorage() { + return inInternalStorage > 0; + } + } +} 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 new file mode 100644 index 000000000..eb6d2b5b5 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingTree.java @@ -0,0 +1,193 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +import com.refinedmods.refinedstorage.api.autocrafting.Ingredient; +import com.refinedmods.refinedstorage.api.autocrafting.Pattern; +import com.refinedmods.refinedstorage.api.autocrafting.PatternRepository; +import com.refinedmods.refinedstorage.api.storage.root.RootStorage; + +import java.util.List; +import javax.annotation.Nullable; + +import static java.util.Objects.requireNonNull; + +class CraftingTree { + private final Pattern pattern; + private final Amount amount; + private final PatternRepository patternRepository; + private final CraftingCalculatorListener listener; + private CraftingState craftingState; + + private CraftingTree(final Pattern pattern, + final CraftingState craftingState, + final Amount amount, + final PatternRepository patternRepository, + final CraftingCalculatorListener listener) { + this.pattern = pattern; + this.craftingState = craftingState; + this.amount = amount; + this.patternRepository = patternRepository; + this.listener = listener; + } + + static CraftingTree root(final Pattern pattern, + final RootStorage rootStorage, + final Amount amount, + final PatternRepository patternRepository, + final CraftingCalculatorListener listener) { + final CraftingState craftingState = CraftingState.of(rootStorage); + return new CraftingTree<>(pattern, craftingState, amount, patternRepository, listener); + } + + static CraftingTree child(final Pattern pattern, + final CraftingState parentState, + final Amount amount, + final PatternRepository patternRepository, + final CraftingCalculatorListener listener) { + return new CraftingTree<>(pattern, parentState.copy(), amount, patternRepository, + listener.childCalculationStarted()); + } + + CalculationResult calculate() { + CalculationResult result = CalculationResult.SUCCESS; + for (final Ingredient ingredient : pattern.getIngredients()) { + if (ingredient.isEmpty()) { + continue; + } + final IngredientState ingredientState = new IngredientState(ingredient, craftingState); + final CalculationResult ingredientResult = calculateIngredient(ingredientState); + if (ingredientResult == CalculationResult.MISSING_RESOURCES) { + result = CalculationResult.MISSING_RESOURCES; + } + } + if (result == CalculationResult.SUCCESS) { + craftingState.addOutputsToInternalStorage(pattern, amount); + } + return result; + } + + private CalculationResult calculateIngredient(final IngredientState ingredientState) { + CraftingState.ResourceState resourceState = craftingState.getResource(ingredientState.get()); + long remaining = ingredientState.amount() * amount.iterations(); + while (remaining > 0) { + if (resourceState.isInInternalStorage()) { + final long toTake = Math.min(remaining, resourceState.inInternalStorage()); + craftingState.extractFromInternalStorage(resourceState.resource(), toTake); + remaining -= toTake; + } + if (remaining > 0 && resourceState.isInStorage()) { + final long toTake = Math.min(remaining, resourceState.inStorage()); + craftingState.extractFromStorage(resourceState.resource(), toTake); + listener.ingredientExtractedFromStorage(resourceState.resource(), toTake); + remaining -= toTake; + } + if (remaining > 0) { + resourceState = tryCalculateChild(ingredientState, resourceState, remaining); + if (resourceState == null) { + return CalculationResult.MISSING_RESOURCES; + } + } + } + return CalculationResult.SUCCESS; + } + + @Nullable + private CraftingState.ResourceState tryCalculateChild(final IngredientState ingredientState, + final CraftingState.ResourceState resourceState, + final long remaining) { + final List childPatterns = patternRepository.getByOutput(resourceState.resource()); + if (!childPatterns.isEmpty()) { + return calculateChild( + ingredientState, + remaining, + childPatterns, + resourceState + ); + } + return ingredientState.cycle().map(craftingState::getResource).orElseGet(() -> { + listener.ingredientsExhausted(resourceState.resource(), remaining); + return null; + }); + } + + @Nullable + private CraftingState.ResourceState calculateChild(final IngredientState ingredientState, + final long remaining, + final List childPatterns, + final CraftingState.ResourceState resourceState) { + final ChildCalculationResult result = calculateChild(remaining, childPatterns, resourceState); + if (result.success) { + this.craftingState = result.childCraftingState; + final CraftingState.ResourceState updatedResourceState = craftingState.getResource( + resourceState.resource() + ); + listener.childCalculationCompleted( + updatedResourceState.resource(), + updatedResourceState.inInternalStorage(), + result.childListener + ); + return updatedResourceState; + } + return cycleToNextIngredientOrFail(ingredientState, resourceState, result); + } + + private ChildCalculationResult calculateChild(final long remaining, + final List childPatterns, + final CraftingState.ResourceState resourceState) { + CraftingTree lastChildTree = null; + Amount lastChildAmount = null; + for (final Pattern childPattern : childPatterns) { + final Amount childAmount = Amount.of(childPattern, resourceState.resource(), remaining); + final CraftingTree childTree = child( + childPattern, + craftingState, + childAmount, + patternRepository, + listener + ); + final CalculationResult childResult = childTree.calculate(); + if (childResult == CalculationResult.MISSING_RESOURCES) { + lastChildTree = childTree; + lastChildAmount = childAmount; + continue; + } + return new ChildCalculationResult<>( + true, + craftingState.getResource(resourceState.resource()).inInternalStorage(), + childTree.listener, + childTree.craftingState + ); + } + return new ChildCalculationResult<>( + false, + requireNonNull(lastChildAmount).getTotal(), + requireNonNull(lastChildTree).listener, + lastChildTree.craftingState + ); + } + + @Nullable + private CraftingState.ResourceState cycleToNextIngredientOrFail(final IngredientState ingredientState, + final CraftingState.ResourceState resourceState, + final ChildCalculationResult result) { + return ingredientState.cycle().map(craftingState::getResource).orElseGet(() -> { + this.craftingState = result.childCraftingState; + listener.childCalculationCompleted( + resourceState.resource(), + result.amountCrafted, + result.childListener + ); + return null; + }); + } + + private record ChildCalculationResult(boolean success, + long amountCrafted, + CraftingCalculatorListener childListener, + CraftingState childCraftingState) { + } + + enum CalculationResult { + SUCCESS, + MISSING_RESOURCES + } +} diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/IngredientState.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/IngredientState.java new file mode 100644 index 000000000..3febc72a7 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/IngredientState.java @@ -0,0 +1,39 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +import com.refinedmods.refinedstorage.api.autocrafting.Ingredient; +import com.refinedmods.refinedstorage.api.resource.ResourceKey; + +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; + +class IngredientState { + private final long amount; + private final List possibilities; + private int pos; + + IngredientState(final Ingredient ingredient, final CraftingState state) { + this.amount = ingredient.getAmount(); + this.possibilities = IntStream.range(0, ingredient.size()) + .mapToObj(ingredient::get) + .sorted(state.storageSorter()) + .sorted(state.internalStorageSorter()) + .toList(); + } + + ResourceKey get() { + return possibilities.get(pos); + } + + long amount() { + return amount; + } + + Optional cycle() { + if (pos + 1 >= possibilities.size()) { + return Optional.empty(); + } + pos++; + return Optional.of(get()); + } +} diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/PreviewCraftingCalculatorListener.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/PreviewCraftingCalculatorListener.java new file mode 100644 index 000000000..031972948 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/PreviewCraftingCalculatorListener.java @@ -0,0 +1,100 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +import com.refinedmods.refinedstorage.api.autocrafting.preview.Preview; +import com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewBuilder; +import com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewType; +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.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PreviewCraftingCalculatorListener + implements CraftingCalculatorListener { + private static final Logger LOGGER = LoggerFactory.getLogger(PreviewCraftingCalculatorListener.class); + + private final UUID listenerId = UUID.randomUUID(); + private PreviewState previewState; + + private PreviewCraftingCalculatorListener(final PreviewState previewState) { + LOGGER.debug("{} - Started calculation", listenerId); + this.previewState = previewState; + } + + public static PreviewCraftingCalculatorListener ofRoot() { + return new PreviewCraftingCalculatorListener(new PreviewState()); + } + + @Override + public CraftingCalculatorListener childCalculationStarted() { + return new PreviewCraftingCalculatorListener(previewState.copy()); + } + + @Override + public void childCalculationCompleted(final ResourceKey resource, + final long amount, + final CraftingCalculatorListener childListener) { + LOGGER.debug("{} - Child calculation completed for {}x {}", listenerId, amount, resource); + this.previewState = ((PreviewCraftingCalculatorListener) childListener).previewState; + this.previewState.toCraft.add(resource, amount); + if (LOGGER.isDebugEnabled()) { + for (final ResourceKey missingResource : previewState.missing.getAll()) { + LOGGER.debug("{} - Missing: {}x {}", listenerId, previewState.missing.get(missingResource), + missingResource); + } + for (final ResourceKey toCraftResource : previewState.toCraft.getAll()) { + LOGGER.debug("{} - To craft: {}x {}", listenerId, previewState.toCraft.get(toCraftResource), + toCraftResource); + } + for (final ResourceKey toTakeFromStorageResource : previewState.toTakeFromStorage.getAll()) { + LOGGER.debug("{} - To take from storage: {}x {}", listenerId, + previewState.toTakeFromStorage.get(toTakeFromStorageResource), toTakeFromStorageResource); + } + } + } + + @Override + public void ingredientsExhausted(final ResourceKey resource, final long amount) { + LOGGER.debug("{} - Ingredients exhausted for {}x {}", listenerId, amount, resource); + previewState.missing.add(resource, amount); + } + + @Override + public void ingredientExtractedFromStorage(final ResourceKey resource, final long amount) { + LOGGER.debug("{} - Extracted from storage: {} - {}", listenerId, resource, amount); + previewState.toTakeFromStorage.add(resource, amount); + } + + public Preview buildPreview() { + final PreviewType previewType = previewState.missing.getAll().isEmpty() + ? PreviewType.SUCCESS + : PreviewType.MISSING_RESOURCES; + final PreviewBuilder previewBuilder = PreviewBuilder.ofType(previewType); + previewState.missing.getAll().forEach(resource -> + previewBuilder.addMissing(resource, previewState.missing.get(resource))); + previewState.toCraft.getAll().forEach(resource -> + previewBuilder.addToCraft(resource, previewState.toCraft.get(resource))); + previewState.toTakeFromStorage.getAll().forEach(resource -> + previewBuilder.addAvailable(resource, previewState.toTakeFromStorage.get(resource))); + return previewBuilder.build(); + } + + public record PreviewState(MutableResourceList toTakeFromStorage, + MutableResourceList toCraft, + MutableResourceList missing) { + private PreviewState() { + this(MutableResourceListImpl.create(), MutableResourceListImpl.create(), MutableResourceListImpl.create()); + } + + private PreviewState copy() { + return new PreviewState( + toTakeFromStorage.copy(), + toCraft.copy(), + missing.copy() + ); + } + } +} diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/package-info.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/package-info.java new file mode 100644 index 000000000..06a860405 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/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/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 new file mode 100644 index 000000000..1c07fde3c --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewBuilder.java @@ -0,0 +1,59 @@ +package com.refinedmods.refinedstorage.api.autocrafting.preview; + +import com.refinedmods.refinedstorage.api.core.CoreValidations; +import com.refinedmods.refinedstorage.api.resource.ResourceKey; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class PreviewBuilder { + private final PreviewType type; + private final Map items = new LinkedHashMap<>(); + + private PreviewBuilder(final PreviewType type) { + this.type = type; + } + + public static PreviewBuilder ofType(final PreviewType type) { + return new PreviewBuilder(type); + } + + private MutablePreviewItem get(final ResourceKey resource) { + return items.computeIfAbsent(resource, key -> new MutablePreviewItem()); + } + + public PreviewBuilder addAvailable(final ResourceKey resource, final long amount) { + CoreValidations.validateLargerThanZero(amount, "Available amount must be larger than 0"); + get(resource).available += amount; + return this; + } + + public PreviewBuilder addMissing(final ResourceKey resource, final long amount) { + CoreValidations.validateLargerThanZero(amount, "Missing amount must be larger than 0"); + get(resource).missing += amount; + return this; + } + + public PreviewBuilder addToCraft(final ResourceKey resource, final long amount) { + CoreValidations.validateLargerThanZero(amount, "To craft amount must be larger than 0"); + get(resource).toCraft += amount; + return this; + } + + public Preview build() { + return new Preview(type, items.entrySet() + .stream() + .map(entry -> entry.getValue().toPreviewItem(entry.getKey())) + .toList()); + } + + private static class MutablePreviewItem { + private long available; + private long missing; + private long toCraft; + + private PreviewItem toPreviewItem(final ResourceKey resource) { + return new PreviewItem(resource, available, missing, toCraft); + } + } +} 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 d69ea48d5..26e8d2b07 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 @@ -7,4 +7,3 @@ public enum PreviewType { SUCCESS, MISSING_RESOURCES } - diff --git a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/PatternBuilder.java b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/PatternBuilder.java new file mode 100644 index 000000000..e125f6347 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/PatternBuilder.java @@ -0,0 +1,56 @@ +package com.refinedmods.refinedstorage.api.autocrafting; + +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; +import com.refinedmods.refinedstorage.api.resource.ResourceKey; + +import java.util.ArrayList; +import java.util.List; + +public class PatternBuilder { + private final List ingredients = new ArrayList<>(); + private final List outputs = new ArrayList<>(); + + private PatternBuilder() { + } + + public static PatternBuilder pattern() { + return new PatternBuilder(); + } + + public IngredientBuilder ingredient(final long amount) { + return new IngredientBuilder(amount); + } + + public PatternBuilder ingredient(final ResourceKey input, final long amount) { + ingredients.add(new Ingredient(amount, List.of(input))); + return this; + } + + public PatternBuilder output(final ResourceKey output, final long amount) { + outputs.add(new ResourceAmount(output, amount)); + return this; + } + + public Pattern build() { + return new PatternImpl(ingredients, outputs.toArray(new ResourceAmount[0])); + } + + public class IngredientBuilder { + private final long amount; + private final List inputs = new ArrayList<>(); + + private IngredientBuilder(final long amount) { + this.amount = amount; + } + + public IngredientBuilder input(final ResourceKey input) { + inputs.add(input); + return this; + } + + public PatternBuilder end() { + ingredients.add(new Ingredient(amount, inputs)); + return PatternBuilder.this; + } + } +} 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 new file mode 100644 index 000000000..7f5a7066d --- /dev/null +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/PatternImpl.java @@ -0,0 +1,44 @@ +package com.refinedmods.refinedstorage.api.autocrafting; + +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; +import com.refinedmods.refinedstorage.api.resource.ResourceKey; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class PatternImpl implements Pattern { + private final List ingredients; + private final List outputs; + + public PatternImpl(final ResourceKey... outputs) { + this(List.of(), + Arrays.stream(outputs).map(output -> new ResourceAmount(output, 1)).toArray(ResourceAmount[]::new)); + } + + public PatternImpl(final List ingredients, final ResourceAmount... outputs) { + this.ingredients = ingredients; + this.outputs = Arrays.asList(outputs); + } + + @Override + public Set getInputResources() { + throw new UnsupportedOperationException(); + } + + @Override + public Set getOutputResources() { + return outputs.stream().map(ResourceAmount::resource).collect(Collectors.toSet()); + } + + @Override + public List getIngredients() { + return ingredients; + } + + @Override + public List getOutputs() { + return outputs; + } +} diff --git a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/PatternRepositoryImplTest.java b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/PatternRepositoryImplTest.java index 8567892e4..e68447ecc 100644 --- a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/PatternRepositoryImplTest.java +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/PatternRepositoryImplTest.java @@ -3,6 +3,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.A; +import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.B; import static org.assertj.core.api.Assertions.assertThat; class PatternRepositoryImplTest { @@ -23,37 +25,76 @@ void testDefaultState() { @Test void shouldAddPattern() { // Act - sut.add(new SimplePattern(ResourceFixtures.A)); + sut.add(new PatternImpl(A)); // Assert - assertThat(sut.getOutputs()).usingRecursiveFieldByFieldElementComparator().containsExactly(ResourceFixtures.A); + assertThat(sut.getOutputs()).usingRecursiveFieldByFieldElementComparator().containsExactly(A); assertThat(sut.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactly( - new SimplePattern(ResourceFixtures.A) + new PatternImpl(A) + ); + assertThat(sut.getByOutput(A)).usingRecursiveFieldByFieldElementComparator().containsExactly( + new PatternImpl(A) ); } @Test void shouldAddMultiplePatterns() { // Act - sut.add(new SimplePattern(ResourceFixtures.A)); - sut.add(new SimplePattern(ResourceFixtures.B)); + sut.add(new PatternImpl(A)); + sut.add(new PatternImpl(B)); // Assert assertThat(sut.getOutputs()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( - ResourceFixtures.A, - ResourceFixtures.B + A, + B ); assertThat(sut.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( - new SimplePattern(ResourceFixtures.A), - new SimplePattern(ResourceFixtures.B) + new PatternImpl(A), + new PatternImpl(B) + ); + assertThat(sut.getByOutput(A)).usingRecursiveFieldByFieldElementComparator().containsExactly( + new PatternImpl(A) + ); + assertThat(sut.getByOutput(B)).usingRecursiveFieldByFieldElementComparator().containsExactly( + new PatternImpl(B) ); + assertThat(sut.getByOutput(ResourceFixtures.C)).isEmpty(); + } + + @Test + void shouldAddMultiplePatternsAndSomeWithTheSameOutput() { + // Arrange + final PatternImpl a = new PatternImpl(A); + final PatternImpl b = new PatternImpl(B, A); + + // Act + sut.add(a); + sut.add(b); + + // Assert + assertThat(sut.getOutputs()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( + A, + B + ); + assertThat(sut.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( + new PatternImpl(A), + new PatternImpl(B, A) + ); + assertThat(sut.getByOutput(A)).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( + new PatternImpl(A), + new PatternImpl(B, A) + ); + assertThat(sut.getByOutput(B)).usingRecursiveFieldByFieldElementComparator().containsExactly( + new PatternImpl(B, A) + ); + assertThat(sut.getByOutput(ResourceFixtures.C)).isEmpty(); } @Test void shouldRemovePattern() { // Arrange - final SimplePattern a = new SimplePattern(ResourceFixtures.A); - final SimplePattern b = new SimplePattern(ResourceFixtures.B); + final PatternImpl a = new PatternImpl(A); + final PatternImpl b = new PatternImpl(B); sut.add(a); sut.add(b); @@ -62,17 +103,21 @@ void shouldRemovePattern() { sut.remove(a); // Assert - assertThat(sut.getOutputs()).usingRecursiveFieldByFieldElementComparator().containsExactly(ResourceFixtures.B); + assertThat(sut.getOutputs()).usingRecursiveFieldByFieldElementComparator().containsExactly(B); assertThat(sut.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactly( - new SimplePattern(ResourceFixtures.B) + new PatternImpl(B) + ); + assertThat(sut.getByOutput(A)).isEmpty(); + assertThat(sut.getByOutput(B)).usingRecursiveFieldByFieldElementComparator().containsExactly( + new PatternImpl(B) ); } @Test void shouldRemoveMultiplePatterns() { // Arrange - final SimplePattern a = new SimplePattern(ResourceFixtures.A); - final SimplePattern b = new SimplePattern(ResourceFixtures.B); + final PatternImpl a = new PatternImpl(A); + final PatternImpl b = new PatternImpl(B); sut.add(a); sut.add(b); @@ -84,13 +129,15 @@ void shouldRemoveMultiplePatterns() { // Assert assertThat(sut.getOutputs()).isEmpty(); assertThat(sut.getAll()).isEmpty(); + assertThat(sut.getByOutput(A)).isEmpty(); + assertThat(sut.getByOutput(B)).isEmpty(); } @Test void shouldRemovePatternButNotRemoveOutputIfAnotherPatternStillHasThatOutput() { // Arrange - final SimplePattern a = new SimplePattern(ResourceFixtures.A); - final SimplePattern b = new SimplePattern(ResourceFixtures.B, ResourceFixtures.A); + final PatternImpl a = new PatternImpl(A); + final PatternImpl b = new PatternImpl(B, A); sut.add(a); sut.add(b); @@ -100,11 +147,31 @@ void shouldRemovePatternButNotRemoveOutputIfAnotherPatternStillHasThatOutput() { // Assert assertThat(sut.getOutputs()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( - ResourceFixtures.A, - ResourceFixtures.B + A, + B ); assertThat(sut.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactly( - new SimplePattern(ResourceFixtures.B, ResourceFixtures.A) + new PatternImpl(B, A) + ); + assertThat(sut.getByOutput(A)).usingRecursiveFieldByFieldElementComparator().containsExactly( + new PatternImpl(B, A) ); + assertThat(sut.getByOutput(B)).usingRecursiveFieldByFieldElementComparator().containsExactly( + new PatternImpl(B, A) + ); + } + + @Test + void shouldRemovePatternThatWasNeverAddedInTheFirstPlace() { + // Arrange + final PatternImpl a = new PatternImpl(A); + + // Act + sut.remove(a); + + // Assert + assertThat(sut.getOutputs()).isEmpty(); + assertThat(sut.getAll()).isEmpty(); + assertThat(sut.getByOutput(A)).isEmpty(); } } diff --git a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/ResourceFixtures.java b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/ResourceFixtures.java index 715577458..cd80c3c53 100644 --- a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/ResourceFixtures.java +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/ResourceFixtures.java @@ -5,5 +5,12 @@ public enum ResourceFixtures implements ResourceKey { A, B, - C + C, + SPRUCE_LOG, + OAK_LOG, + OAK_PLANKS, + SPRUCE_PLANKS, + CRAFTING_TABLE, + STICKS, + SIGN } diff --git a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/SimplePattern.java b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/SimplePattern.java deleted file mode 100644 index 9f9a7c1eb..000000000 --- a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/SimplePattern.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.refinedmods.refinedstorage.api.autocrafting; - -import com.refinedmods.refinedstorage.api.resource.ResourceKey; - -import java.util.Set; - -class SimplePattern implements Pattern { - private final Set outputs; - - SimplePattern(final ResourceKey... outputs) { - this.outputs = Set.of(outputs); - } - - @Override - public Set getInputResources() { - return Set.of(); - } - - @Override - public Set getOutputResources() { - return 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..918b882dd --- /dev/null +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/calculation/CraftingCalculatorImplTest.java @@ -0,0 +1,578 @@ +package com.refinedmods.refinedstorage.api.autocrafting.calculation; + +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.preview.Preview; +import com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewBuilder; +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; + +import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +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 com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.SIGN; +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.PreviewType.MISSING_RESOURCES; +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; + +class CraftingCalculatorImplTest { + private static final RecursiveComparisonConfiguration PREVIEW_CONFIG = RecursiveComparisonConfiguration.builder() + .withIgnoredCollectionOrderInFields("items") + .build(); + + @Test + void shouldNotCalculateForPatternThatIsNotFound() { + // Arrange + final RootStorage storage = storage(); + final PatternRepository patterns = patterns(); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Executable action = () -> calculateAndGetPreview(sut, CRAFTING_TABLE, 1); + + // Assert + final IllegalStateException e = assertThrows(IllegalStateException.class, action); + assertThat(e).hasMessage("No pattern found for " + CRAFTING_TABLE); + } + + @ParameterizedTest + @ValueSource(longs = {-1, 0}) + void shouldNotCalculateWithInvalidRequestedAmount(final long requestedAmount) { + // Arrange + final RootStorage storage = storage(); + final PatternRepository patterns = patterns(); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Executable action = () -> calculateAndGetPreview(sut, CRAFTING_TABLE, requestedAmount); + + // Assert + final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, action); + assertThat(e).hasMessage("Requested amount must be greater than 0"); + } + + @ParameterizedTest + @ValueSource(longs = {1, 2}) + void shouldCalculateForSingleRootPatternSingleIngredientAndAllResourcesAreAvailable( + final long requestedAmount + ) { + // Arrange + final RootStorage storage = storage(new ResourceAmount(OAK_PLANKS, 8)); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, requestedAmount); + + // Assert + assertThat(preview).usingRecursiveComparison().isEqualTo(PreviewBuilder.ofType(SUCCESS) + .addToCraft(CRAFTING_TABLE, requestedAmount) + .addAvailable(OAK_PLANKS, requestedAmount * 4) + .build()); + } + + @Test + void shouldNotCalculateForSingleRootPatternSingleIngredientAndAlmostAllResourcesAreAvailable() { + // Arrange + final RootStorage storage = storage(new ResourceAmount(OAK_PLANKS, 8)); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 3); + + // Assert + assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG) + .isEqualTo(PreviewBuilder.ofType(MISSING_RESOURCES) + .addToCraft(CRAFTING_TABLE, 3) + .addAvailable(OAK_PLANKS, 8) + .addMissing(OAK_PLANKS, 4) + .build()); + } + + @Test + void shouldCalculateWithSingleRootPatternWithMultipleIngredientAndMultipleAreCraftableButOnly1HasEnoughResources() { + // Arrange + final RootStorage storage = storage( + new ResourceAmount(SPRUCE_LOG, 1) + ); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 4) + .build(), + pattern() + .ingredient(SPRUCE_LOG, 1) + .output(SPRUCE_PLANKS, 4) + .build(), + pattern() + .ingredient(4).input(OAK_PLANKS).input(SPRUCE_PLANKS).end() + .output(CRAFTING_TABLE, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 1); + + // Assert + assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(SUCCESS) + .addToCraft(CRAFTING_TABLE, 1) + .addToCraft(SPRUCE_PLANKS, 4) + .addAvailable(SPRUCE_LOG, 1) + .build()); + } + + @Test + void shouldPrioritizeResourcesThatWeHaveMostOfInStorageForSingleRootPatternAndMultipleIngredients() { + // Arrange + final RootStorage storage = storage( + new ResourceAmount(OAK_PLANKS, 4 * 10), + new ResourceAmount(SPRUCE_PLANKS, 4 * 5) + ); + final PatternRepository patterns = patterns( + pattern() + .ingredient(4).input(SPRUCE_PLANKS).input(OAK_PLANKS).end() + .output(CRAFTING_TABLE, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 11); + + // Assert + assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(SUCCESS) + .addToCraft(CRAFTING_TABLE, 11) + .addAvailable(OAK_PLANKS, 4 * 10) + .addAvailable(SPRUCE_PLANKS, 4) + .build()); + } + + @Test + void shouldExhaustAllPossibleIngredientsWhenRunningOutInSingleRootPatternAndMultipleIngredients() { + // Arrange + final RootStorage storage = storage( + new ResourceAmount(OAK_PLANKS, 4 * 10), + new ResourceAmount(SPRUCE_PLANKS, 4 * 5) + ); + final PatternRepository patterns = patterns( + pattern() + .ingredient(4).input(OAK_PLANKS).input(SPRUCE_PLANKS).end() + .output(CRAFTING_TABLE, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 16); + + // Assert + assertThat(preview) + .usingRecursiveComparison(PREVIEW_CONFIG) + .isEqualTo(PreviewBuilder.ofType(MISSING_RESOURCES) + .addToCraft(CRAFTING_TABLE, 16) + .addAvailable(OAK_PLANKS, 4 * 10) + .addAvailable(SPRUCE_PLANKS, 4 * 5) + .addMissing(SPRUCE_PLANKS, 4) + .build()); + } + + @Test + void shouldExhaustAllPossibleIngredientsWhenRunningOutInSingleRootPatternAndMultipleCraftableIngredients() { + // Arrange + final RootStorage storage = storage(); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 4) + .build(), + pattern() + .ingredient(SPRUCE_LOG, 1) + .output(SPRUCE_PLANKS, 4) + .build(), + pattern() + .ingredient(4).input(OAK_PLANKS).input(SPRUCE_PLANKS).end() + .output(CRAFTING_TABLE, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 1); + + // Assert + assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(MISSING_RESOURCES) + .addToCraft(CRAFTING_TABLE, 1) + .addToCraft(SPRUCE_PLANKS, 4) + .addMissing(SPRUCE_LOG, 1) + .build()); + } + + @Test + void shouldCalculateForMultipleRootPatternsAndSingleIngredientAndAllResourcesAreAvailable() { + // Arrange + final RootStorage storage = storage( + new ResourceAmount(SPRUCE_PLANKS, 8) + ); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build(), + pattern() + .ingredient(SPRUCE_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 2); + + // Assert + assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(SUCCESS) + .addToCraft(CRAFTING_TABLE, 2) + .addAvailable(SPRUCE_PLANKS, 8) + .build()); + } + + @Test + void shouldNotCalculateForMultipleRootPatternsAndSingleIngredientAndAlmostAllResourcesAreAvailable() { + // Arrange + final RootStorage storage = storage( + new ResourceAmount(SPRUCE_PLANKS, 8) + ); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build(), + pattern() + .ingredient(SPRUCE_PLANKS, 8) + .output(CRAFTING_TABLE, 2) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculateAndGetPreview(sut, CRAFTING_TABLE, 3); + + // Assert + assertThat(preview) + .usingRecursiveComparison(PREVIEW_CONFIG) + .isEqualTo(PreviewBuilder.ofType(MISSING_RESOURCES) + .addToCraft(CRAFTING_TABLE, 4) + .addAvailable(SPRUCE_PLANKS, 8) + .addMissing(SPRUCE_PLANKS, 8) + .build()); + } + + @Test + void shouldCalculateForSingleRootPatternAndSingleChildPatternWithSingleIngredientAndAllResourcesAreAvailable() { + // Arrange + final RootStorage storage = storage( + new ResourceAmount(OAK_PLANKS, 3), + new ResourceAmount(OAK_LOG, 3) + ); + 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 = calculateAndGetPreview(sut, CRAFTING_TABLE, 3); + + // Assert + assertThat(preview) + .usingRecursiveComparison(PREVIEW_CONFIG) + .isEqualTo(PreviewBuilder.ofType(SUCCESS) + .addToCraft(CRAFTING_TABLE, 3) + .addAvailable(OAK_PLANKS, 3) + .addToCraft(OAK_PLANKS, 12) + .addAvailable(OAK_LOG, 3) + .build()); + } + + @Test + void shouldNotCalculateForSingleRootPatternSingleChildPatternWSingleIngredientAndAlmostAllResourcesAreAvailable() { + // Arrange + final RootStorage storage = storage( + new ResourceAmount(OAK_LOG, 2) + ); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 4) + .build(), + pattern() + .ingredient(SPRUCE_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 = calculateAndGetPreview(sut, CRAFTING_TABLE, 3); + + // Assert + assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(MISSING_RESOURCES) + .addToCraft(CRAFTING_TABLE, 3) + .addToCraft(OAK_PLANKS, 12) + .addMissing(SPRUCE_LOG, 3) + .build()); + } + + @Test + void shouldCraftMoreIfNecessaryIfResourcesFromInternalStorageAreUsedUp() { + // Arrange + final RootStorage storage = storage( + new ResourceAmount(OAK_LOG, 4) + ); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 2) + .build(), + pattern() + .ingredient(OAK_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build(), + pattern() + .ingredient(OAK_PLANKS, 2) + .output(STICKS, 4) + .build(), + pattern() + .ingredient(OAK_PLANKS, 6) + .ingredient(STICKS, 1) + .output(SIGN, 3) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculateAndGetPreview(sut, SIGN, 1); + + // Assert + assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(SUCCESS) + .addToCraft(SIGN, 3) + .addToCraft(OAK_PLANKS, 8) + .addAvailable(OAK_LOG, 4) + .addToCraft(STICKS, 4) + .build()); + } + + @Test + void shouldCraftMoreIfNecessaryIfResourcesFromStorageAreUsedUp() { + // Arrange + final RootStorage storage = storage( + new ResourceAmount(OAK_PLANKS, 6), + new ResourceAmount(OAK_LOG, 1) + ); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 2) + .build(), + pattern() + .ingredient(OAK_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build(), + pattern() + .ingredient(OAK_PLANKS, 2) + .output(STICKS, 4) + .build(), + pattern() + .ingredient(OAK_PLANKS, 6) + .ingredient(STICKS, 1) + .output(SIGN, 3) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculateAndGetPreview(sut, SIGN, 1); + + // Assert + assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(SUCCESS) + .addToCraft(SIGN, 3) + .addAvailable(OAK_PLANKS, 6) + .addToCraft(STICKS, 4) + .addToCraft(OAK_PLANKS, 2) + .addAvailable(OAK_LOG, 1) + .build()); + } + + private static Stream provideMissingResourcesPreview() { + return Stream.of( + Arguments.of(1, PreviewBuilder.ofType(MISSING_RESOURCES) + .addToCraft(SIGN, 3) + .addToCraft(OAK_PLANKS, 8) + .addAvailable(OAK_LOG, 3) // 6 + .addMissing(OAK_LOG, 1) // 2 + .addToCraft(STICKS, 4) + .build()), + Arguments.of(4, PreviewBuilder.ofType(MISSING_RESOURCES) + .addToCraft(SIGN, 6) + .addToCraft(OAK_PLANKS, 14) + .addAvailable(OAK_LOG, 3) // 6 + .addMissing(OAK_LOG, 4) // 4*2=8 + .addToCraft(STICKS, 4) + .build()), + Arguments.of(20, PreviewBuilder.ofType(MISSING_RESOURCES) + .addToCraft(SIGN, 21) // these are 7 iterations (3 yield per, 7*3=21) + .addToCraft(OAK_PLANKS, 46) // 4 planks are used for the sticks (4*2=8). + // that remains 46-4=42 + // 42/7 iterations=6 so checks out + .addAvailable(OAK_LOG, 3) + .addMissing(OAK_LOG, 20) // we need 46 planks. we already have 3*2=6 planks! + // 46-6=40 planks left to craft + // 40 planks/2 planks per log=20 logs + .addToCraft(STICKS, 8) // 8*3=21 + .build() + )); + } + + @ParameterizedTest + @MethodSource("provideMissingResourcesPreview") + void shouldKeepCalculatingEvenIfResourcesAreMissing( + final long requestedAmount, + final Preview expectedPreview + ) { + // Arrange + final RootStorage storage = storage(new ResourceAmount(OAK_LOG, 3)); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 2) + .build(), + pattern() + .ingredient(OAK_PLANKS, 4) + .output(CRAFTING_TABLE, 1) + .build(), + pattern() + .ingredient(OAK_PLANKS, 2) + .output(STICKS, 4) + .build(), + pattern() + .ingredient(OAK_PLANKS, 6) + .ingredient(STICKS, 1) + .output(SIGN, 3) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculateAndGetPreview(sut, SIGN, requestedAmount); + + // Assert + assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(expectedPreview); + } + + private static Stream provideAmounts() { + return Stream.of( + Arguments.arguments(1, 4, 1), + Arguments.arguments(2, 4, 1), + Arguments.arguments(3, 4, 1), + Arguments.arguments(4, 4, 1), + Arguments.arguments(5, 8, 2), + Arguments.arguments(6, 8, 2) + ); + } + + @ParameterizedTest + @MethodSource("provideAmounts") + void shouldCraftCorrectAmountWhenNotRequestingAMultipleOfThePatternOutputAmount( + final long requestedAmount, + final long planksCrafted, + final long logsUsed + ) { + // Arrange + final RootStorage storage = storage(new ResourceAmount(OAK_LOG, 30)); + final PatternRepository patterns = patterns( + pattern() + .ingredient(OAK_LOG, 1) + .output(OAK_PLANKS, 2) + .output(OAK_PLANKS, 2) + .output(STICKS, 1) + .build() + ); + final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage); + + // Act + final Preview preview = calculateAndGetPreview(sut, OAK_PLANKS, requestedAmount); + + // Assert + assertThat(preview).usingRecursiveComparison(PREVIEW_CONFIG).isEqualTo(PreviewBuilder.ofType(SUCCESS) + .addToCraft(OAK_PLANKS, planksCrafted) + .addAvailable(OAK_LOG, logsUsed) + .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(); + } + + 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; + } + + private 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/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 new file mode 100644 index 000000000..af5fc6379 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/preview/PreviewBuilderTest.java @@ -0,0 +1,99 @@ +package com.refinedmods.refinedstorage.api.autocrafting.preview; + +import java.util.Collections; +import java.util.List; + +import org.assertj.core.api.ThrowableAssert; +import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; +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.ResourceFixtures.OAK_LOG; +import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.OAK_PLANKS; +import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.SPRUCE_LOG; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +class PreviewBuilderTest { + private static final RecursiveComparisonConfiguration CONFIG = RecursiveComparisonConfiguration.builder() + .withIgnoredCollectionOrderInFields("items") + .build(); + + @Test + void testDefaultState() { + // Act + final Preview preview = PreviewBuilder.ofType(PreviewType.SUCCESS).build(); + + // Assert + assertThat(preview).usingRecursiveComparison() + .isEqualTo(new Preview(PreviewType.SUCCESS, Collections.emptyList())); + } + + @Test + void testPreview() { + // Act + final Preview preview = PreviewBuilder.ofType(PreviewType.MISSING_RESOURCES) + .addToCraft(OAK_PLANKS, 4) + .addToCraft(OAK_PLANKS, 1) + .addAvailable(OAK_LOG, 1) + .addAvailable(OAK_LOG, 2) + .addMissing(SPRUCE_LOG, 1) + .addMissing(SPRUCE_LOG, 2) + .build(); + + // Assert + assertThat(preview) + .usingRecursiveComparison(CONFIG) + .isEqualTo(new Preview(PreviewType.MISSING_RESOURCES, List.of( + new PreviewItem(OAK_PLANKS, 0, 0, 5), + new PreviewItem(OAK_LOG, 3, 0, 0), + new PreviewItem(SPRUCE_LOG, 0, 3, 0) + ))); + } + + @ParameterizedTest + @ValueSource(longs = {-1, 0}) + void testToCraftMustBeLargerThanZero(final long amount) { + // Arrange + final PreviewBuilder builder = PreviewBuilder.ofType(PreviewType.MISSING_RESOURCES); + + // Act + final ThrowableAssert.ThrowingCallable action = () -> builder.addToCraft(OAK_PLANKS, amount); + + // Assert + assertThatThrownBy(action) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("To craft amount must be larger than 0"); + } + + @ParameterizedTest + @ValueSource(longs = {-1, 0}) + void testMissingMustBeLargerThanZero(final long amount) { + // Arrange + final PreviewBuilder builder = PreviewBuilder.ofType(PreviewType.MISSING_RESOURCES); + + // Act + final ThrowableAssert.ThrowingCallable action = () -> builder.addMissing(OAK_PLANKS, amount); + + // Assert + assertThatThrownBy(action) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Missing amount must be larger than 0"); + } + + @ParameterizedTest + @ValueSource(longs = {-1, 0}) + void testAvailableMustBeLargerThanZero(final long amount) { + // Arrange + final PreviewBuilder builder = PreviewBuilder.ofType(PreviewType.MISSING_RESOURCES); + + // Act + final ThrowableAssert.ThrowingCallable action = () -> builder.addAvailable(OAK_PLANKS, amount); + + // Assert + assertThatThrownBy(action) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Available amount must be larger than 0"); + } +} diff --git a/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/preview/package-info.java b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/preview/package-info.java new file mode 100644 index 000000000..bf08171a5 --- /dev/null +++ b/refinedstorage-autocrafting-api/src/test/java/com/refinedmods/refinedstorage/api/autocrafting/preview/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@FieldsAndMethodsAreNonnullByDefault +package com.refinedmods.refinedstorage.api.autocrafting.preview; + +import com.refinedmods.refinedstorage.api.core.FieldsAndMethodsAreNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/AbstractModInitializer.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/AbstractModInitializer.java index 63d5aeb23..18c443aa4 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/AbstractModInitializer.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/AbstractModInitializer.java @@ -274,7 +274,9 @@ private void registerNetworkComponents() { ); RefinedStorageApi.INSTANCE.getNetworkComponentMapFactory().addFactory( AutocraftingNetworkComponent.class, - network -> new AutocraftingNetworkComponentImpl(new TaskStatusProviderImpl()) + network -> new AutocraftingNetworkComponentImpl( + () -> network.getComponent(StorageNetworkComponent.class), + new TaskStatusProviderImpl()) ); } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/CraftingPattern.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/CraftingPattern.java index 46513896c..0e5c42426 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/CraftingPattern.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/CraftingPattern.java @@ -1,5 +1,6 @@ package com.refinedmods.refinedstorage.common.autocrafting; +import com.refinedmods.refinedstorage.api.autocrafting.Ingredient; import com.refinedmods.refinedstorage.api.autocrafting.Pattern; import com.refinedmods.refinedstorage.api.resource.ResourceAmount; import com.refinedmods.refinedstorage.api.resource.ResourceKey; @@ -10,12 +11,13 @@ import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.Stream; class CraftingPattern implements Pattern { private final UUID id; - private final List> inputs; + private final List ingredients; private final ResourceAmount output; - private final List byproducts; + private final List outputs; private final Set inputResources; private final Set outputResources; @@ -24,11 +26,11 @@ class CraftingPattern implements Pattern { final ResourceAmount output, final List byproducts) { this.id = id; - this.inputs = inputs; this.output = output; this.inputResources = inputs.stream().flatMap(List::stream).collect(Collectors.toSet()); this.outputResources = Set.of(output.resource()); - this.byproducts = byproducts; + this.ingredients = inputs.stream().map(i -> new Ingredient(1, i)).toList(); + this.outputs = Stream.concat(Stream.of(output), byproducts.stream()).toList(); } @Override @@ -41,8 +43,14 @@ public Set getInputResources() { return inputResources; } - List> getInputs() { - return inputs; + @Override + public List getIngredients() { + return ingredients; + } + + @Override + public List getOutputs() { + return outputs; } ResourceAmount getOutput() { diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/CraftingPatternClientTooltipComponent.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/CraftingPatternClientTooltipComponent.java index 56be06912..4e28038bf 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/CraftingPatternClientTooltipComponent.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/CraftingPatternClientTooltipComponent.java @@ -1,10 +1,10 @@ package com.refinedmods.refinedstorage.common.autocrafting; +import com.refinedmods.refinedstorage.api.autocrafting.Ingredient; +import com.refinedmods.refinedstorage.api.resource.ResourceKey; import com.refinedmods.refinedstorage.common.api.RefinedStorageClientApi; -import com.refinedmods.refinedstorage.common.api.support.resource.PlatformResourceKey; import com.refinedmods.refinedstorage.common.support.resource.ItemResource; -import java.util.List; import javax.annotation.Nullable; import net.minecraft.ChatFormatting; @@ -95,17 +95,18 @@ private void renderInputSlots(final int x, final int y, final GuiGraphics graphi private void renderInputSlot(final int x, final int y, final GuiGraphics graphics, final int sx, final int sy) { graphics.blitSprite(SLOT, x + sx * 18, y + sy * 18, 18, 18); final int index = sy * width + sx; - final List inputs = craftingPattern.getInputs().get(index); - if (!inputs.isEmpty()) { - final int idx = currentCycle % inputs.size(); - final PlatformResourceKey resource = inputs.get(idx); - RefinedStorageClientApi.INSTANCE.getResourceRendering(resource.getClass()).render( - resource, - graphics, - x + sx * 18 + 1, - y + sy * 18 + 1 - ); + final Ingredient ingredient = craftingPattern.getIngredients().get(index); + if (ingredient.isEmpty()) { + return; } + final int idx = currentCycle % ingredient.size(); + final ResourceKey resource = ingredient.get(idx); + RefinedStorageClientApi.INSTANCE.getResourceRendering(resource.getClass()).render( + resource, + graphics, + x + sx * 18 + 1, + y + sy * 18 + 1 + ); } private void renderArrow(final int x, final int y, final GuiGraphics graphics) { diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/PatternItem.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/PatternItem.java index 34bb97c5d..ce91d7d40 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/PatternItem.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/PatternItem.java @@ -245,7 +245,9 @@ private Optional getProcessingPattern(final UUID id, final ItemStack st if (state == null) { return Optional.empty(); } - return Optional.of(new ProcessingPattern(id, state.getFlatInputs(), state.getFlatOutputs())); + return Optional.of( + new ProcessingPattern(id, state.getFlatInputs(), state.getIngredients(), state.getFlatOutputs()) + ); } private Optional getStonecutterPattern(final UUID id, final ItemStack stack, final Level level) { diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPattern.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPattern.java index c1784e076..f0ed69479 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPattern.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPattern.java @@ -1,5 +1,6 @@ package com.refinedmods.refinedstorage.common.autocrafting; +import com.refinedmods.refinedstorage.api.autocrafting.Ingredient; import com.refinedmods.refinedstorage.api.autocrafting.Pattern; import com.refinedmods.refinedstorage.api.resource.ResourceAmount; import com.refinedmods.refinedstorage.api.resource.ResourceKey; @@ -13,13 +14,18 @@ class ProcessingPattern implements Pattern { private final UUID id; private final List inputs; + private final List ingredients; private final List outputs; private final Set inputResources; private final Set outputResources; - ProcessingPattern(final UUID id, final List inputs, final List outputs) { + ProcessingPattern(final UUID id, + final List inputs, + final List ingredients, + final List outputs) { this.id = id; this.inputs = inputs; + this.ingredients = ingredients; this.outputs = outputs; this.inputResources = inputs.stream().map(ResourceAmount::resource).collect(Collectors.toSet()); this.outputResources = outputs.stream().map(ResourceAmount::resource).collect(Collectors.toSet()); @@ -31,14 +37,20 @@ public Set getOutputResources() { } @Override - public Set getInputResources() { - return inputResources; + public List getIngredients() { + return ingredients; } - List getOutputs() { + @Override + public List getOutputs() { return outputs; } + @Override + public Set getInputResources() { + return inputResources; + } + @Override public boolean equals(final Object o) { if (this == o) { diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPatternClientTooltipComponent.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPatternClientTooltipComponent.java index 7b76bfdb8..7e0448a1f 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPatternClientTooltipComponent.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPatternClientTooltipComponent.java @@ -1,13 +1,14 @@ package com.refinedmods.refinedstorage.common.autocrafting; +import com.refinedmods.refinedstorage.api.autocrafting.Ingredient; import com.refinedmods.refinedstorage.api.resource.ResourceAmount; +import com.refinedmods.refinedstorage.api.resource.ResourceKey; import com.refinedmods.refinedstorage.common.api.RefinedStorageClientApi; -import com.refinedmods.refinedstorage.common.api.support.resource.PlatformResourceKey; import com.refinedmods.refinedstorage.common.api.support.resource.ResourceRendering; import com.refinedmods.refinedstorage.common.support.ResourceSlotRendering; +import java.util.Collections; import java.util.List; -import java.util.stream.Stream; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.Font; @@ -21,12 +22,14 @@ import static com.refinedmods.refinedstorage.common.support.Sprites.SLOT; class ProcessingPatternClientTooltipComponent implements ClientTooltipComponent { + private static final Ingredient EMPTY_INGREDIENT = new Ingredient(0, Collections.emptyList()); + private static final long CYCLE_MS = 1000; private static final int ARROW_SPACING = 8; private final int rows; private final List outputTexts; - private final List> inputs; + private final List ingredients; private final List> outputs; private long cycleStart = 0; @@ -35,24 +38,19 @@ class ProcessingPatternClientTooltipComponent implements ClientTooltipComponent ProcessingPatternClientTooltipComponent(final ProcessingPatternState state) { this.rows = calculateMaxRows(state); this.outputTexts = getOutputText(state); - this.inputs = state.inputs().stream().map(input -> input.map(mainInput -> - Stream.concat( - Stream.of(mainInput.input()), - mainInput.allowedAlternativeIds().stream() - .filter(id -> mainInput.input().resource() instanceof PlatformResourceKey) - .map(id -> (PlatformResourceKey) mainInput.input().resource()) - .flatMap(resource -> resource.getTags().stream() - .filter(tag -> mainInput.allowedAlternativeIds().contains(tag.key().location())) - .flatMap(tag -> tag.resources().stream()) - .map(tagEntry -> new ResourceAmount(tagEntry, mainInput.input().amount()))) - )).orElse(Stream.empty()).toList()).toList(); + this.ingredients = state.ingredients() + .stream() + .map(processingIngredient -> + processingIngredient.map(ProcessingPatternState.ProcessingIngredient::toIngredient) + .orElse(EMPTY_INGREDIENT) + ).toList(); this.outputs = state.outputs().stream().map(output -> output.map(List::of).orElse(List.of())).toList(); } private static int calculateMaxRows(final ProcessingPatternState state) { int lastFilledInputIndex = 0; - for (int i = 0; i < state.inputs().size(); i++) { - if (state.inputs().get(i).isPresent()) { + for (int i = 0; i < state.ingredients().size(); i++) { + if (state.ingredients().get(i).isPresent()) { lastFilledInputIndex = i; } } @@ -121,21 +119,41 @@ private void renderMatrixSlots(final int x, final int y, final boolean input, final GuiGraphics graphics) { - final List> slots = input ? inputs : outputs; + final int maxSize = input ? ingredients.size() : outputs.size(); for (int row = 0; row < rows; ++row) { for (int column = 0; column < 3; ++column) { final int slotXOffset = !input ? ((18 * 3) + ARROW_SPACING + LIGHT_ARROW_WIDTH + ARROW_SPACING) : 0; final int slotX = x + slotXOffset + column * 18; final int slotY = y + row * 18; final int idx = row * 3 + column; - if (idx >= slots.size()) { + if (idx >= maxSize) { break; } - renderMatrixSlot(graphics, slotX, slotY, slots.get(idx)); + if (input) { + renderMatrixSlot(graphics, slotX, slotY, ingredients.get(idx)); + } else { + renderMatrixSlot(graphics, slotX, slotY, outputs.get(idx)); + } } } } + private void renderMatrixSlot( + final GuiGraphics graphics, + final int slotX, + final int slotY, + final Ingredient ingredient + ) { + graphics.blitSprite(SLOT, slotX, slotY, 18, 18); + if (ingredient.isEmpty()) { + return; + } + final ResourceKey resource = ingredient.get(currentCycle % ingredient.size()); + final ResourceRendering rendering = RefinedStorageClientApi.INSTANCE.getResourceRendering(resource.getClass()); + rendering.render(resource, graphics, slotX + 1, slotY + 1); + ResourceSlotRendering.renderAmount(graphics, slotX + 1, slotY + 1, ingredient.getAmount(), rendering); + } + private void renderMatrixSlot( final GuiGraphics graphics, final int slotX, diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPatternState.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPatternState.java index f6ca770a0..3a78d8afd 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPatternState.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/ProcessingPatternState.java @@ -1,13 +1,17 @@ package com.refinedmods.refinedstorage.common.autocrafting; +import com.refinedmods.refinedstorage.api.autocrafting.Ingredient; 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; +import com.refinedmods.refinedstorage.common.api.support.resource.PlatformResourceKey; import com.refinedmods.refinedstorage.common.support.resource.ResourceCodecs; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; @@ -17,26 +21,35 @@ import net.minecraft.resources.ResourceLocation; public record ProcessingPatternState( - List> inputs, + List> ingredients, List> outputs ) { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - Codec.list(Input.OPTIONAL_CODEC).fieldOf("inputs").forGetter(ProcessingPatternState::inputs), - Codec.list(ResourceCodecs.AMOUNT_OPTIONAL_CODEC).fieldOf("outputs").forGetter(ProcessingPatternState::outputs) + Codec.list(ProcessingIngredient.OPTIONAL_CODEC).fieldOf("ingredients") + .forGetter(ProcessingPatternState::ingredients), + Codec.list(ResourceCodecs.AMOUNT_OPTIONAL_CODEC).fieldOf("outputs") + .forGetter(ProcessingPatternState::outputs) ).apply(instance, ProcessingPatternState::new)); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( - ByteBufCodecs.collection(ArrayList::new, Input.OPTIONAL_STREAM_CODEC), - ProcessingPatternState::inputs, + ByteBufCodecs.collection(ArrayList::new, ProcessingIngredient.OPTIONAL_STREAM_CODEC), + ProcessingPatternState::ingredients, ByteBufCodecs.collection(ArrayList::new, ResourceCodecs.AMOUNT_STREAM_OPTIONAL_CODEC), ProcessingPatternState::outputs, ProcessingPatternState::new ); + List getIngredients() { + return ingredients + .stream() + .flatMap(ingredient -> ingredient.map(ProcessingIngredient::toIngredient).stream()) + .toList(); + } + List getFlatInputs() { final MutableResourceList list = MutableResourceListImpl.orderPreserving(); - inputs.forEach(input -> input.map(Input::input).ifPresent(list::add)); + ingredients.forEach(ingredient -> ingredient.map(ProcessingIngredient::input).ifPresent(list::add)); return new ArrayList<>(list.copyState()); } @@ -46,21 +59,41 @@ List getFlatOutputs() { return new ArrayList<>(list.copyState()); } - public record Input(ResourceAmount input, List allowedAlternativeIds) { - public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - ResourceCodecs.AMOUNT_CODEC.fieldOf("input").forGetter(Input::input), - Codec.list(ResourceLocation.CODEC).fieldOf("allowedAlternativeIds").forGetter(Input::allowedAlternativeIds) - ).apply(instance, Input::new)); - public static final Codec> OPTIONAL_CODEC = CODEC.optionalFieldOf("input").codec(); + public record ProcessingIngredient(ResourceAmount input, List allowedAlternativeIds) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + ResourceCodecs.AMOUNT_CODEC.fieldOf("input") + .forGetter(ProcessingIngredient::input), + Codec.list(ResourceLocation.CODEC).fieldOf("allowedAlternativeIds") + .forGetter(ProcessingIngredient::allowedAlternativeIds) + ).apply(instance, ProcessingIngredient::new)); + public static final Codec> OPTIONAL_CODEC = + CODEC.optionalFieldOf("input").codec(); - public static final StreamCodec STREAM_CODEC = StreamCodec.composite( - ResourceCodecs.AMOUNT_STREAM_CODEC, - Input::input, - ByteBufCodecs.collection(ArrayList::new, ResourceLocation.STREAM_CODEC), - Input::allowedAlternativeIds, - Input::new - ); - public static final StreamCodec> OPTIONAL_STREAM_CODEC = ByteBufCodecs - .optional(STREAM_CODEC); + public static final StreamCodec STREAM_CODEC = + StreamCodec.composite( + ResourceCodecs.AMOUNT_STREAM_CODEC, + ProcessingIngredient::input, + ByteBufCodecs.collection(ArrayList::new, ResourceLocation.STREAM_CODEC), + ProcessingIngredient::allowedAlternativeIds, + ProcessingIngredient::new + ); + public static final StreamCodec> OPTIONAL_STREAM_CODEC = + ByteBufCodecs.optional(STREAM_CODEC); + + public Ingredient toIngredient() { + return new Ingredient(input.amount(), calculateInputsIncludingAlternatives()); + } + + private List calculateInputsIncludingAlternatives() { + return Stream.concat( + Stream.of(input.resource()), + allowedAlternativeIds.stream() + .filter(id -> input.resource() instanceof PlatformResourceKey) + .map(id -> (PlatformResourceKey) input.resource()) + .flatMap(resource -> resource.getTags().stream() + .filter(tag -> allowedAlternativeIds.contains(tag.key().location())) + .flatMap(tag -> tag.resources().stream())) + ).toList(); + } } } diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/SmithingTablePattern.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/SmithingTablePattern.java index fb7d0bc0c..27c4f9d38 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/SmithingTablePattern.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/SmithingTablePattern.java @@ -1,9 +1,12 @@ package com.refinedmods.refinedstorage.common.autocrafting; +import com.refinedmods.refinedstorage.api.autocrafting.Ingredient; 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.common.support.resource.ItemResource; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -13,7 +16,9 @@ class SmithingTablePattern implements Pattern { private final ItemResource template; private final ItemResource base; private final ItemResource addition; + private final List ingredients; private final ItemResource output; + private final List outputs; private final Set inputResources; private final Set outputResources; @@ -26,16 +31,32 @@ class SmithingTablePattern implements Pattern { this.template = template; this.base = base; this.addition = addition; + this.ingredients = List.of(single(template), single(base), single(addition)); this.output = output; + this.outputs = List.of(new ResourceAmount(output, 1)); this.inputResources = Set.of(template, base, addition); this.outputResources = Set.of(output); } + private static Ingredient single(final ResourceKey input) { + return new Ingredient(1, List.of(input)); + } + @Override public Set getOutputResources() { return outputResources; } + @Override + public List getIngredients() { + return ingredients; + } + + @Override + public List getOutputs() { + return outputs; + } + @Override public Set getInputResources() { return inputResources; diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/StonecutterPattern.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/StonecutterPattern.java index c87d99454..892ba1cda 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/StonecutterPattern.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/StonecutterPattern.java @@ -1,9 +1,12 @@ package com.refinedmods.refinedstorage.common.autocrafting; +import com.refinedmods.refinedstorage.api.autocrafting.Ingredient; 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.common.support.resource.ItemResource; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -12,6 +15,8 @@ class StonecutterPattern implements Pattern { private final UUID id; private final ItemResource input; private final ItemResource output; + private final List ingredients; + private final List outputs; private final Set inputResources; private final Set outputResources; @@ -21,6 +26,8 @@ class StonecutterPattern implements Pattern { this.output = output; this.inputResources = Set.of(input); this.outputResources = Set.of(output); + this.outputs = List.of(new ResourceAmount(output, 1)); + this.ingredients = List.of(new Ingredient(1, List.of(input))); } @Override @@ -28,6 +35,16 @@ public Set getOutputResources() { return outputResources; } + @Override + public List getIngredients() { + return ingredients; + } + + @Override + public List getOutputs() { + return outputs; + } + @Override public Set getInputResources() { return inputResources; diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/patterngrid/PatternGridBlockEntity.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/patterngrid/PatternGridBlockEntity.java index 73e51b010..4697107a2 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/patterngrid/PatternGridBlockEntity.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/patterngrid/PatternGridBlockEntity.java @@ -315,16 +315,16 @@ private ItemStack createProcessingPattern() { return null; } final ItemStack result = createPatternStack(PatternType.PROCESSING); - final List> inputs = new ArrayList<>(); + final List> ingredients = new ArrayList<>(); for (int i = 0; i < processingInput.size(); ++i) { - inputs.add(processingInput.getInput(i)); + ingredients.add(processingInput.getIngredient(i)); } final List> outputs = new ArrayList<>(); for (int i = 0; i < processingOutput.size(); ++i) { outputs.add(Optional.ofNullable(processingOutput.get(i))); } final ProcessingPatternState patternProcessingState = new ProcessingPatternState( - inputs, + ingredients, outputs ); result.set(DataComponents.INSTANCE.getProcessingPatternState(), patternProcessingState); @@ -439,9 +439,9 @@ private void copyCraftingPattern(final CraftingPatternState state) { private void copyProcessingPattern(final ProcessingPatternState state) { clearProcessing(); - for (int i = 0; i < state.inputs().size(); ++i) { + for (int i = 0; i < state.ingredients().size(); ++i) { final int ii = i; - state.inputs().get(i).ifPresent(input -> processingInput.set(ii, input)); + state.ingredients().get(i).ifPresent(processingIngredient -> processingInput.set(ii, processingIngredient)); } for (int i = 0; i < state.outputs().size(); ++i) { final int ii = i; diff --git a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/patterngrid/ProcessingMatrixInputResourceContainer.java b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/patterngrid/ProcessingMatrixInputResourceContainer.java index d97b09830..574b4e808 100644 --- a/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/patterngrid/ProcessingMatrixInputResourceContainer.java +++ b/refinedstorage-common/src/main/java/com/refinedmods/refinedstorage/common/autocrafting/patterngrid/ProcessingMatrixInputResourceContainer.java @@ -42,21 +42,21 @@ class ProcessingMatrixInputResourceContainer extends ResourceContainerImpl { } } - void set(final int index, final ProcessingPatternState.Input input) { - setSilently(index, input.input()); - allowedTagIds.set(index, new HashSet<>(input.allowedAlternativeIds())); + void set(final int index, final ProcessingPatternState.ProcessingIngredient processingIngredient) { + setSilently(index, processingIngredient.input()); + allowedTagIds.set(index, new HashSet<>(processingIngredient.allowedAlternativeIds())); changed(); } - Optional getInput(final int index) { - return Optional.ofNullable(get(index)).map(input -> getInput(index, input)); + Optional getIngredient(final int index) { + return Optional.ofNullable(get(index)).map(input -> getIngredient(index, input)); } - private ProcessingPatternState.Input getInput(final int index, final ResourceAmount input) { + private ProcessingPatternState.ProcessingIngredient getIngredient(final int index, final ResourceAmount input) { final List ids = allowedTagIds.get(index) == null ? Collections.emptyList() : new ArrayList<>(allowedTagIds.get(index)); - return new ProcessingPatternState.Input(input, ids); + return new ProcessingPatternState.ProcessingIngredient(input, ids); } Set getAllowedTagIds(final int index) { diff --git a/refinedstorage-network-test/src/main/java/com/refinedmods/refinedstorage/network/test/fixtures/NetworkTestFixtures.java b/refinedstorage-network-test/src/main/java/com/refinedmods/refinedstorage/network/test/fixtures/NetworkTestFixtures.java index 86c0261f5..56556166b 100644 --- a/refinedstorage-network-test/src/main/java/com/refinedmods/refinedstorage/network/test/fixtures/NetworkTestFixtures.java +++ b/refinedstorage-network-test/src/main/java/com/refinedmods/refinedstorage/network/test/fixtures/NetworkTestFixtures.java @@ -39,7 +39,12 @@ public final class NetworkTestFixtures { ); NETWORK_COMPONENT_MAP_FACTORY.addFactory( AutocraftingNetworkComponent.class, - network -> new AutocraftingNetworkComponentImpl(new FakeTaskStatusProvider()) + network -> new AutocraftingNetworkComponentImpl( + () -> { + throw new UnsupportedOperationException("Storage not accessible from here (yet)"); + }, + new FakeTaskStatusProvider() + ) ); } 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 ed4e2741c..6017d73d7 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 @@ -3,9 +3,10 @@ import com.refinedmods.refinedstorage.api.autocrafting.Pattern; import com.refinedmods.refinedstorage.api.autocrafting.PatternRepositoryImpl; import com.refinedmods.refinedstorage.api.autocrafting.TaskId; +import com.refinedmods.refinedstorage.api.autocrafting.calculation.CraftingCalculator; +import com.refinedmods.refinedstorage.api.autocrafting.calculation.CraftingCalculatorImpl; +import com.refinedmods.refinedstorage.api.autocrafting.calculation.PreviewCraftingCalculatorListener; 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.autocrafting.status.TaskStatus; import com.refinedmods.refinedstorage.api.autocrafting.status.TaskStatusListener; import com.refinedmods.refinedstorage.api.autocrafting.status.TaskStatusProvider; @@ -15,20 +16,24 @@ import com.refinedmods.refinedstorage.api.network.autocrafting.PatternProvider; import com.refinedmods.refinedstorage.api.network.node.container.NetworkNodeContainer; import com.refinedmods.refinedstorage.api.resource.ResourceKey; +import com.refinedmods.refinedstorage.api.storage.root.RootStorage; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; public class AutocraftingNetworkComponentImpl implements AutocraftingNetworkComponent, ParentContainer { + private final Supplier rootStorageProvider; private final Set providers = new HashSet<>(); private final Set listeners = new HashSet<>(); private final PatternRepositoryImpl patternRepository = new PatternRepositoryImpl(); private final TaskStatusProvider taskStatusProvider; - public AutocraftingNetworkComponentImpl(final TaskStatusProvider taskStatusProvider) { + public AutocraftingNetworkComponentImpl(final Supplier rootStorageProvider, + final TaskStatusProvider taskStatusProvider) { + this.rootStorageProvider = rootStorageProvider; this.taskStatusProvider = taskStatusProvider; } @@ -72,19 +77,11 @@ public boolean contains(final AutocraftingNetworkComponent component) { @Override public Optional getPreview(final ResourceKey resource, final long amount) { - final List items = new ArrayList<>(); - final boolean missing = amount == 404; - for (int i = 0; i < 31; ++i) { - items.add(new PreviewItem( - resource, - (i + 1), - (i % 2 == 0 && missing) ? amount : 0, - i % 2 == 0 ? 0 : amount - )); - } - return Optional.of(new Preview(missing - ? PreviewType.MISSING_RESOURCES - : PreviewType.SUCCESS, items)); + 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()); } @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 2683c6deb..002603538 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 @@ -1,28 +1,48 @@ package com.refinedmods.refinedstorage.api.network.impl.autocrafting; +import com.refinedmods.refinedstorage.api.autocrafting.Ingredient; import com.refinedmods.refinedstorage.api.autocrafting.Pattern; import com.refinedmods.refinedstorage.api.autocrafting.TaskId; +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.autocrafting.status.TaskStatus; import com.refinedmods.refinedstorage.api.autocrafting.status.TaskStatusListener; +import com.refinedmods.refinedstorage.api.core.Action; import com.refinedmods.refinedstorage.api.network.autocrafting.PatternListener; import com.refinedmods.refinedstorage.api.network.impl.node.patternprovider.PatternProviderNetworkNode; import com.refinedmods.refinedstorage.api.network.node.container.NetworkNodeContainer; +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; import com.refinedmods.refinedstorage.network.test.fixtures.FakeTaskStatusProvider; +import java.util.List; +import java.util.Optional; import java.util.UUID; +import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static com.refinedmods.refinedstorage.network.test.fixtures.ResourceFixtures.A; +import static com.refinedmods.refinedstorage.network.test.fixtures.ResourceFixtures.B; import static org.assertj.core.api.Assertions.assertThat; class AutocraftingNetworkComponentImplTest { + private static final RecursiveComparisonConfiguration PREVIEW_CONFIG = RecursiveComparisonConfiguration.builder() + .withIgnoredCollectionOrderInFields("items") + .build(); + + private RootStorage rootStorage; private AutocraftingNetworkComponentImpl sut; @BeforeEach void setUp() { - sut = new AutocraftingNetworkComponentImpl(new FakeTaskStatusProvider()); + rootStorage = new RootStorageImpl(); + sut = new AutocraftingNetworkComponentImpl(() -> rootStorage, new FakeTaskStatusProvider()); } @Test @@ -68,7 +88,7 @@ public void taskAdded(final TaskStatus status) { void shouldAddPatternsFromPatternProvider() { // Arrange final PatternProviderNetworkNode provider = new PatternProviderNetworkNode(0, 5); - provider.setPattern(1, new SimplePattern(A)); + provider.setPattern(1, new PatternImpl(A)); final NetworkNodeContainer container = () -> provider; @@ -83,7 +103,7 @@ void shouldAddPatternsFromPatternProvider() { void shouldRemovePatternsFromPatternProvider() { // Arrange final PatternProviderNetworkNode provider = new PatternProviderNetworkNode(0, 5); - provider.setPattern(1, new SimplePattern(A)); + provider.setPattern(1, new PatternImpl(A)); final NetworkNodeContainer container = () -> provider; sut.onContainerAdded(container); @@ -98,7 +118,7 @@ void shouldRemovePatternsFromPatternProvider() { @Test void shouldAddPatternManually() { // Arrange - final SimplePattern pattern = new SimplePattern(A); + final PatternImpl pattern = new PatternImpl(A); // Act sut.add(pattern); @@ -110,7 +130,7 @@ void shouldAddPatternManually() { @Test void shouldRemovePatternManually() { // Arrange - final SimplePattern pattern = new SimplePattern(A); + final PatternImpl pattern = new PatternImpl(A); sut.add(pattern); // Act @@ -127,6 +147,25 @@ void shouldStartTask() { @Test void shouldGetPreview() { - sut.getPreview(A, 10); + // Arrange + rootStorage.addSource(new StorageImpl()); + rootStorage.insert(A, 10, Action.EXECUTE, EmptyActor.INSTANCE); + + sut.add(new PatternImpl( + List.of(new Ingredient(3, List.of(A))), + new ResourceAmount(B, 1) + )); + + // Act + final Optional preview = sut.getPreview(B, 2); + + // Assert + assertThat(preview) + .get() + .usingRecursiveComparison(PREVIEW_CONFIG) + .isEqualTo(new Preview(PreviewType.SUCCESS, List.of( + new PreviewItem(B, 0, 0, 2), + new PreviewItem(A, 6, 0, 0) + ))); } } diff --git a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/PatternImpl.java b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/PatternImpl.java new file mode 100644 index 000000000..71a420dce --- /dev/null +++ b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/PatternImpl.java @@ -0,0 +1,46 @@ +package com.refinedmods.refinedstorage.api.network.impl.autocrafting; + +import com.refinedmods.refinedstorage.api.autocrafting.Ingredient; +import com.refinedmods.refinedstorage.api.autocrafting.Pattern; +import com.refinedmods.refinedstorage.api.resource.ResourceAmount; +import com.refinedmods.refinedstorage.api.resource.ResourceKey; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class PatternImpl implements Pattern { + private final List ingredients; + private final List outputs; + + public PatternImpl(final ResourceKey... outputs) { + this(List.of(), + Arrays.stream(outputs).map(output -> new ResourceAmount(output, 1)).toArray(ResourceAmount[]::new)); + } + + public PatternImpl(final List ingredients, final ResourceAmount... outputs) { + this.ingredients = ingredients; + this.outputs = Arrays.asList(outputs); + } + + @Override + public Set getInputResources() { + throw new UnsupportedOperationException(); + } + + @Override + public Set getOutputResources() { + return outputs.stream().map(ResourceAmount::resource).collect(Collectors.toSet()); + } + + @Override + public List getIngredients() { + return ingredients; + } + + @Override + public List getOutputs() { + return outputs; + } +} diff --git a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/SimplePattern.java b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/SimplePattern.java deleted file mode 100644 index 25669b377..000000000 --- a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/SimplePattern.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.refinedmods.refinedstorage.api.network.impl.autocrafting; - -import com.refinedmods.refinedstorage.api.autocrafting.Pattern; -import com.refinedmods.refinedstorage.api.resource.ResourceKey; - -import java.util.Set; - -public class SimplePattern implements Pattern { - private final Set outputs; - - public SimplePattern(final ResourceKey... outputs) { - this.outputs = Set.of(outputs); - } - - @Override - public Set getInputResources() { - return Set.of(); - } - - @Override - public Set getOutputResources() { - return outputs; - } -} diff --git a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/node/patternprovider/PatternProviderNetworkNodeTest.java b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/node/patternprovider/PatternProviderNetworkNodeTest.java index a8c39a174..745988766 100644 --- a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/node/patternprovider/PatternProviderNetworkNodeTest.java +++ b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/node/patternprovider/PatternProviderNetworkNodeTest.java @@ -1,7 +1,7 @@ package com.refinedmods.refinedstorage.api.network.impl.node.patternprovider; import com.refinedmods.refinedstorage.api.network.autocrafting.AutocraftingNetworkComponent; -import com.refinedmods.refinedstorage.api.network.impl.autocrafting.SimplePattern; +import com.refinedmods.refinedstorage.api.network.impl.autocrafting.PatternImpl; import com.refinedmods.refinedstorage.network.test.AddNetworkNode; import com.refinedmods.refinedstorage.network.test.InjectNetworkAutocraftingComponent; import com.refinedmods.refinedstorage.network.test.NetworkTest; @@ -35,7 +35,7 @@ void shouldSetPatternAndNotifyNetwork( @InjectNetworkAutocraftingComponent final AutocraftingNetworkComponent autocrafting ) { // Act - final SimplePattern pattern = new SimplePattern(A); + final PatternImpl pattern = new PatternImpl(A); sut.setPattern(0, pattern); // Assert @@ -47,7 +47,7 @@ void shouldRemovePatternAndNotifyNetwork( @InjectNetworkAutocraftingComponent final AutocraftingNetworkComponent autocrafting ) { // Arrange - final SimplePattern pattern = new SimplePattern(A); + final PatternImpl pattern = new PatternImpl(A); sut.setPattern(0, pattern); // Act @@ -62,11 +62,11 @@ void shouldReplacePatternAndNotifyNetwork( @InjectNetworkAutocraftingComponent final AutocraftingNetworkComponent autocrafting ) { // Arrange - final SimplePattern pattern = new SimplePattern(A); + final PatternImpl pattern = new PatternImpl(A); sut.setPattern(0, pattern); // Act - final SimplePattern replacedPattern = new SimplePattern(B); + final PatternImpl replacedPattern = new PatternImpl(B); sut.setPattern(0, replacedPattern); // Assert @@ -78,7 +78,7 @@ void shouldRemovePatternsFromNetworkWhenInactive( @InjectNetworkAutocraftingComponent final AutocraftingNetworkComponent autocrafting ) { // Arrange - final SimplePattern pattern = new SimplePattern(A); + final PatternImpl pattern = new PatternImpl(A); sut.setPattern(0, pattern); // Act @@ -93,7 +93,7 @@ void shouldAddPatternsFromNetworkWhenActive( @InjectNetworkAutocraftingComponent final AutocraftingNetworkComponent autocrafting ) { // Arrange - final SimplePattern pattern = new SimplePattern(A); + final PatternImpl pattern = new PatternImpl(A); sut.setPattern(0, pattern); sut.setActive(false); diff --git a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/node/relay/RelayNetworkNodeTest.java b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/node/relay/RelayNetworkNodeTest.java index 78135c04e..ca816e6bf 100644 --- a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/node/relay/RelayNetworkNodeTest.java +++ b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/node/relay/RelayNetworkNodeTest.java @@ -5,7 +5,7 @@ import com.refinedmods.refinedstorage.api.network.Network; import com.refinedmods.refinedstorage.api.network.autocrafting.AutocraftingNetworkComponent; import com.refinedmods.refinedstorage.api.network.energy.EnergyNetworkComponent; -import com.refinedmods.refinedstorage.api.network.impl.autocrafting.SimplePattern; +import com.refinedmods.refinedstorage.api.network.impl.autocrafting.PatternImpl; import com.refinedmods.refinedstorage.api.network.impl.node.patternprovider.PatternProviderNetworkNode; import com.refinedmods.refinedstorage.api.network.impl.security.SecurityDecisionProviderImpl; import com.refinedmods.refinedstorage.api.network.node.container.NetworkNodeContainer; @@ -469,7 +469,7 @@ static void addStorageSource(final StorageNetworkComponent storage) { } static Runnable addPattern(final AutocraftingNetworkComponent component, final ResourceKey output) { - final Pattern pattern = new SimplePattern(output); + final Pattern pattern = new PatternImpl(output); final PatternProviderNetworkNode patternProvider = new PatternProviderNetworkNode(0, 1); patternProvider.setPattern(0, pattern); final NetworkNodeContainer container = () -> patternProvider;