From b7e632b92dd156ab321a18e7ceabe71907f4fd0a Mon Sep 17 00:00:00 2001 From: Raoul Date: Mon, 4 Nov 2019 14:35:38 +0100 Subject: [PATCH] Pattern grid changes (#2344) * Begin work on input configuration * Sync * Saving * Readability * Apply * Invalidate pattern when declared tags are no longer applicable * Move pattern creation to a factory * Localize error message * Add flag for input config slots * Apply allowed tags when pattern is inserted * Fix ctor * Initial layout * When putting a pattern, use processing from pattern * When jei transferring, switch to correct mode --- .../api/autocrafting/ICraftingPattern.java | 13 +- .../apiimpl/autocrafting/CraftingPattern.java | 168 ++------ .../autocrafting/CraftingPatternFactory.java | 185 +++++++++ .../CraftingPatternFactoryException.java | 15 + .../network/grid/CraftingGridBehavior.java | 6 + .../apiimpl/network/node/AllowedTags.java | 114 ++++++ .../apiimpl/network/node/GridNetworkNode.java | 64 ++- .../container/GridContainer.java | 14 +- .../InputConfigurationContainer.java | 9 + .../container/slot/filter/FilterSlot.java | 7 +- .../slot/filter/FluidFilterSlot.java | 5 + .../jei/GridRecipeTransferHandler.java | 30 +- .../refinedstorage/item/PatternItem.java | 62 ++- .../grid/GridProcessingTransferMessage.java | 3 + .../refinedstorage/screen/BaseScreen.java | 53 ++- .../screen/grid/GridScreen.java | 6 +- .../screen/grid/InputConfigurationScreen.java | 385 ++++++++++++++++++ .../screen/widget/CheckBoxWidget.java | 17 +- .../tile/data/RSSerializers.java | 47 ++- .../refinedstorage/tile/grid/GridTile.java | 15 + .../assets/refinedstorage/lang/en_us.json | 9 + .../textures/gui/input_configuration.png | Bin 0 -> 1675 bytes 22 files changed, 1034 insertions(+), 193 deletions(-) create mode 100644 src/main/java/com/raoulvdberge/refinedstorage/apiimpl/autocrafting/CraftingPatternFactory.java create mode 100644 src/main/java/com/raoulvdberge/refinedstorage/apiimpl/autocrafting/CraftingPatternFactoryException.java create mode 100644 src/main/java/com/raoulvdberge/refinedstorage/apiimpl/network/node/AllowedTags.java create mode 100644 src/main/java/com/raoulvdberge/refinedstorage/container/InputConfigurationContainer.java create mode 100644 src/main/java/com/raoulvdberge/refinedstorage/screen/grid/InputConfigurationScreen.java create mode 100644 src/main/resources/assets/refinedstorage/textures/gui/input_configuration.png diff --git a/src/main/java/com/raoulvdberge/refinedstorage/api/autocrafting/ICraftingPattern.java b/src/main/java/com/raoulvdberge/refinedstorage/api/autocrafting/ICraftingPattern.java index 747d35978..4a882095f 100755 --- a/src/main/java/com/raoulvdberge/refinedstorage/api/autocrafting/ICraftingPattern.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/api/autocrafting/ICraftingPattern.java @@ -3,8 +3,10 @@ package com.raoulvdberge.refinedstorage.api.autocrafting; import net.minecraft.item.ItemStack; import net.minecraft.util.NonNullList; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; import net.minecraftforge.fluids.FluidStack; +import javax.annotation.Nullable; import java.util.List; /** @@ -26,16 +28,17 @@ public interface ICraftingPattern { */ boolean isValid(); + /** + * @return an error message when this pattern is not valid, or null if there's no message + */ + @Nullable + ITextComponent getErrorMessage(); + /** * @return true if the crafting pattern can be treated as a processing pattern, false otherwise */ boolean isProcessing(); - /** - * @return true if the crafting pattern is in exact mode, false otherwise - */ - boolean isExact(); - /** * @return the inputs per slot */ diff --git a/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/autocrafting/CraftingPattern.java b/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/autocrafting/CraftingPattern.java index 6890d1bb3..ffd6c04f7 100755 --- a/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/autocrafting/CraftingPattern.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/autocrafting/CraftingPattern.java @@ -5,142 +5,57 @@ import com.raoulvdberge.refinedstorage.api.autocrafting.ICraftingPatternContaine import com.raoulvdberge.refinedstorage.api.util.IComparer; import com.raoulvdberge.refinedstorage.apiimpl.API; import com.raoulvdberge.refinedstorage.apiimpl.autocrafting.registry.CraftingTaskFactory; -import com.raoulvdberge.refinedstorage.item.PatternItem; +import com.raoulvdberge.refinedstorage.apiimpl.network.node.AllowedTags; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.fluid.Fluid; import net.minecraft.inventory.CraftingInventory; import net.minecraft.inventory.container.Container; -import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.ICraftingRecipe; -import net.minecraft.item.crafting.IRecipeType; -import net.minecraft.tags.FluidTags; -import net.minecraft.tags.ItemTags; import net.minecraft.util.NonNullList; import net.minecraft.util.ResourceLocation; -import net.minecraft.world.World; +import net.minecraft.util.text.ITextComponent; import net.minecraftforge.fluids.FluidStack; -import java.util.ArrayList; +import javax.annotation.Nullable; import java.util.List; -import java.util.Optional; public class CraftingPattern implements ICraftingPattern { - private ICraftingPatternContainer container; - private ItemStack stack; - private boolean processing; - private boolean exact; - private boolean valid; - private ICraftingRecipe recipe; - private List> inputs = new ArrayList<>(); - private NonNullList outputs = NonNullList.create(); - private NonNullList byproducts = NonNullList.create(); - private List> fluidInputs = new ArrayList<>(); - private NonNullList fluidOutputs = NonNullList.create(); + private final ICraftingPatternContainer container; + private final ItemStack stack; + private final boolean processing; + private final boolean exact; + private final boolean valid; + @Nullable + private final ITextComponent errorMessage; + @Nullable + private final ICraftingRecipe recipe; + private final List> inputs; + private final NonNullList outputs; + private final NonNullList byproducts; + private final List> fluidInputs; + private final NonNullList fluidOutputs; + @Nullable + private final AllowedTags allowedTags; - public CraftingPattern(World world, ICraftingPatternContainer container, ItemStack stack) { + public CraftingPattern(ICraftingPatternContainer container, ItemStack stack, boolean processing, boolean exact, @Nullable ITextComponent errorMessage, boolean valid, @Nullable ICraftingRecipe recipe, List> inputs, NonNullList outputs, NonNullList byproducts, List> fluidInputs, NonNullList fluidOutputs, @Nullable AllowedTags allowedTags) { this.container = container; this.stack = stack; - this.processing = PatternItem.isProcessing(stack); - this.exact = PatternItem.isExact(stack); + this.processing = processing; + this.exact = exact; + this.valid = valid; + this.errorMessage = errorMessage; + this.recipe = recipe; + this.inputs = inputs; + this.outputs = outputs; + this.byproducts = byproducts; + this.fluidInputs = fluidInputs; + this.fluidOutputs = fluidOutputs; + this.allowedTags = allowedTags; + } - if (processing) { - for (int i = 0; i < 9; ++i) { - { - ItemStack input = PatternItem.getInputSlot(stack, i); - - if (input.isEmpty()) { - inputs.add(NonNullList.create()); - } else if (!exact) { - NonNullList possibilities = NonNullList.create(); - - possibilities.add(input.copy()); - - for (ResourceLocation owningTag : ItemTags.getCollection().getOwningTags(input.getItem())) { - for (Item element : ItemTags.getCollection().get(owningTag).getAllElements()) { - possibilities.add(new ItemStack(element, input.getCount())); - } - } - - inputs.add(possibilities); - } else { - inputs.add(NonNullList.from(ItemStack.EMPTY, input)); - } - - ItemStack output = PatternItem.getOutputSlot(stack, i); - if (!output.isEmpty()) { - this.valid = true; // As soon as we have one output, we are valid. - - outputs.add(output); - } - } - - { - FluidStack fluidInput = PatternItem.getFluidInputSlot(stack, i); - if (fluidInput.isEmpty()) { - fluidInputs.add(NonNullList.create()); - } else if (!exact) { - NonNullList possibilities = NonNullList.create(); - - possibilities.add(fluidInput.copy()); - - for (ResourceLocation owningTag : FluidTags.getCollection().getOwningTags(fluidInput.getFluid())) { - for (Fluid element : FluidTags.getCollection().get(owningTag).getAllElements()) { - possibilities.add(new FluidStack(element, fluidInput.getAmount())); - } - } - - fluidInputs.add(possibilities); - } else { - fluidInputs.add(NonNullList.from(FluidStack.EMPTY, fluidInput)); - } - } - - FluidStack fluidOutput = PatternItem.getFluidOutputSlot(stack, i); - if (!fluidOutput.isEmpty()) { - this.valid = true; - - fluidOutputs.add(fluidOutput); - } - } - } else { - CraftingInventory inv = new DummyCraftingInventory(); - - for (int i = 0; i < 9; ++i) { - ItemStack input = PatternItem.getInputSlot(stack, i); - - inputs.add(input.isEmpty() ? NonNullList.create() : NonNullList.from(ItemStack.EMPTY, input)); - - inv.setInventorySlotContents(i, input); - } - - Optional potentialRecipe = world.getRecipeManager().getRecipe(IRecipeType.CRAFTING, inv, world); - if (potentialRecipe.isPresent()) { - this.recipe = potentialRecipe.get(); - - this.byproducts = recipe.getRemainingItems(inv); - - ItemStack output = recipe.getCraftingResult(inv); - - if (!output.isEmpty()) { - this.valid = true; - - outputs.add(output); - - if (!exact) { - if (recipe.getIngredients().size() > 0) { - inputs.clear(); - - for (int i = 0; i < recipe.getIngredients().size(); ++i) { - inputs.add(i, NonNullList.from(ItemStack.EMPTY, recipe.getIngredients().get(i).getMatchingStacks())); - } - } else { - this.valid = false; - } - } - } - } - } + @Nullable + public AllowedTags getAllowedTags() { + return allowedTags; } @Override @@ -158,14 +73,15 @@ public class CraftingPattern implements ICraftingPattern { return valid; } + @Nullable @Override - public boolean isProcessing() { - return processing; + public ITextComponent getErrorMessage() { + return errorMessage; } @Override - public boolean isExact() { - return exact; + public boolean isProcessing() { + return processing; } @Override @@ -255,7 +171,7 @@ public class CraftingPattern implements ICraftingPattern { @Override public boolean canBeInChainWith(ICraftingPattern other) { - if (other.isProcessing() != processing || other.isExact() != exact) { + if (other.isProcessing() != processing) { return false; } @@ -357,7 +273,7 @@ public class CraftingPattern implements ICraftingPattern { return result; } - private class DummyCraftingInventory extends CraftingInventory { + public static class DummyCraftingInventory extends CraftingInventory { public DummyCraftingInventory() { super(new Container(null, 0) { @Override diff --git a/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/autocrafting/CraftingPatternFactory.java b/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/autocrafting/CraftingPatternFactory.java new file mode 100644 index 000000000..3a032ef3b --- /dev/null +++ b/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/autocrafting/CraftingPatternFactory.java @@ -0,0 +1,185 @@ +package com.raoulvdberge.refinedstorage.apiimpl.autocrafting; + +import com.raoulvdberge.refinedstorage.api.autocrafting.ICraftingPatternContainer; +import com.raoulvdberge.refinedstorage.apiimpl.network.node.AllowedTags; +import com.raoulvdberge.refinedstorage.item.PatternItem; +import net.minecraft.fluid.Fluid; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.ICraftingRecipe; +import net.minecraft.item.crafting.IRecipeType; +import net.minecraft.tags.FluidTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraft.world.World; +import net.minecraftforge.fluids.FluidStack; + +import javax.annotation.Nullable; +import java.util.*; + +public class CraftingPatternFactory { + public static final CraftingPatternFactory INSTANCE = new CraftingPatternFactory(); + + public CraftingPattern create(World world, ICraftingPatternContainer container, ItemStack stack) { + boolean processing = PatternItem.isProcessing(stack); + boolean exact = PatternItem.isExact(stack); + AllowedTags allowedTags = PatternItem.getAllowedTags(stack); + + List> inputs = new ArrayList<>(); + NonNullList outputs = NonNullList.create(); + NonNullList byproducts = NonNullList.create(); + List> fluidInputs = new ArrayList<>(); + NonNullList fluidOutputs = NonNullList.create(); + ICraftingRecipe recipe = null; + boolean valid = true; + ITextComponent errorMessage = null; + + try { + if (processing) { + for (int i = 0; i < 9; ++i) { + fillProcessingInputs(i, stack, inputs, outputs, allowedTags); + fillProcessingFluidInputs(i, stack, fluidInputs, fluidOutputs, allowedTags); + } + + if (outputs.isEmpty() && fluidOutputs.isEmpty()) { + throw new CraftingPatternFactoryException(new TranslationTextComponent("misc.refinedstorage.pattern.error.processing_no_outputs")); + } + } else { + CraftingInventory inv = new CraftingPattern.DummyCraftingInventory(); + + for (int i = 0; i < 9; ++i) { + fillCraftingInputs(inv, stack, inputs, i); + } + + Optional foundRecipe = world.getRecipeManager().getRecipe(IRecipeType.CRAFTING, inv, world); + if (foundRecipe.isPresent()) { + recipe = foundRecipe.get(); + + byproducts = recipe.getRemainingItems(inv); + + ItemStack output = recipe.getCraftingResult(inv); + + if (!output.isEmpty()) { + outputs.add(output); + + if (!exact) { + modifyCraftingInputsToUseAlternatives(recipe, inputs); + } + } else { + throw new CraftingPatternFactoryException(new TranslationTextComponent("misc.refinedstorage.pattern.error.no_output")); + } + } else { + throw new CraftingPatternFactoryException(new TranslationTextComponent("misc.refinedstorage.pattern.error.recipe_does_not_exist")); + } + } + } catch (CraftingPatternFactoryException e) { + valid = false; + errorMessage = e.getErrorMessage(); + } + + return new CraftingPattern(container, stack, processing, exact, errorMessage, valid, recipe, inputs, outputs, byproducts, fluidInputs, fluidOutputs, allowedTags); + } + + private void fillProcessingInputs(int i, ItemStack stack, List> inputs, NonNullList outputs, @Nullable AllowedTags allowedTags) throws CraftingPatternFactoryException { + ItemStack input = PatternItem.getInputSlot(stack, i); + + if (input.isEmpty()) { + inputs.add(NonNullList.create()); + } else { + NonNullList possibilities = NonNullList.create(); + + possibilities.add(input.copy()); + + if (allowedTags != null) { + Collection tagsOfItem = ItemTags.getCollection().getOwningTags(input.getItem()); + Set declaredAllowedTags = allowedTags.getAllowedItemTags().get(i); + + for (ResourceLocation declaredAllowedTag : declaredAllowedTags) { + if (!tagsOfItem.contains(declaredAllowedTag)) { + throw new CraftingPatternFactoryException( + new TranslationTextComponent( + "misc.refinedstorage.pattern.error.tag_no_longer_applicable", + declaredAllowedTag.toString(), + input.getDisplayName() + ) + ); + } else { + for (Item element : ItemTags.getCollection().get(declaredAllowedTag).getAllElements()) { + possibilities.add(new ItemStack(element, input.getCount())); + } + } + } + } + + inputs.add(possibilities); + } + + ItemStack output = PatternItem.getOutputSlot(stack, i); + if (!output.isEmpty()) { + outputs.add(output); + } + } + + private void fillProcessingFluidInputs(int i, ItemStack stack, List> fluidInputs, NonNullList fluidOutputs, @Nullable AllowedTags allowedTags) throws CraftingPatternFactoryException { + FluidStack input = PatternItem.getFluidInputSlot(stack, i); + if (input.isEmpty()) { + fluidInputs.add(NonNullList.create()); + } else { + NonNullList possibilities = NonNullList.create(); + + possibilities.add(input.copy()); + + if (allowedTags != null) { + Collection tagsOfFluid = FluidTags.getCollection().getOwningTags(input.getFluid()); + Set declaredAllowedTags = allowedTags.getAllowedFluidTags().get(i); + + for (ResourceLocation declaredAllowedTag : declaredAllowedTags) { + if (!tagsOfFluid.contains(declaredAllowedTag)) { + throw new CraftingPatternFactoryException( + new TranslationTextComponent( + "misc.refinedstorage.pattern.error.tag_no_longer_applicable", + declaredAllowedTag.toString(), + input.getDisplayName() + ) + ); + } else { + for (Fluid element : FluidTags.getCollection().get(declaredAllowedTag).getAllElements()) { + possibilities.add(new FluidStack(element, input.getAmount())); + } + } + } + } + + fluidInputs.add(possibilities); + } + + FluidStack output = PatternItem.getFluidOutputSlot(stack, i); + if (!output.isEmpty()) { + fluidOutputs.add(output); + } + } + + private void fillCraftingInputs(CraftingInventory inv, ItemStack stack, List> inputs, int i) { + ItemStack input = PatternItem.getInputSlot(stack, i); + + inputs.add(input.isEmpty() ? NonNullList.create() : NonNullList.from(ItemStack.EMPTY, input)); + + inv.setInventorySlotContents(i, input); + } + + private void modifyCraftingInputsToUseAlternatives(ICraftingRecipe recipe, List> inputs) throws CraftingPatternFactoryException { + if (!recipe.getIngredients().isEmpty()) { + inputs.clear(); + + for (int i = 0; i < recipe.getIngredients().size(); ++i) { + inputs.add(i, NonNullList.from(ItemStack.EMPTY, recipe.getIngredients().get(i).getMatchingStacks())); + } + } else { + throw new CraftingPatternFactoryException(new TranslationTextComponent("misc.refinedstorage.pattern.error.recipe_no_ingredients")); + } + } +} diff --git a/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/autocrafting/CraftingPatternFactoryException.java b/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/autocrafting/CraftingPatternFactoryException.java new file mode 100644 index 000000000..97208cc97 --- /dev/null +++ b/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/autocrafting/CraftingPatternFactoryException.java @@ -0,0 +1,15 @@ +package com.raoulvdberge.refinedstorage.apiimpl.autocrafting; + +import net.minecraft.util.text.ITextComponent; + +public class CraftingPatternFactoryException extends Exception { + private final ITextComponent errorMessage; + + public CraftingPatternFactoryException(ITextComponent errorMessage) { + this.errorMessage = errorMessage; + } + + public ITextComponent getErrorMessage() { + return errorMessage; + } +} diff --git a/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/network/grid/CraftingGridBehavior.java b/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/network/grid/CraftingGridBehavior.java index 7564d307d..27990fba1 100644 --- a/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/network/grid/CraftingGridBehavior.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/network/grid/CraftingGridBehavior.java @@ -8,6 +8,7 @@ import com.raoulvdberge.refinedstorage.api.network.security.Permission; import com.raoulvdberge.refinedstorage.api.util.Action; import com.raoulvdberge.refinedstorage.api.util.IComparer; import com.raoulvdberge.refinedstorage.apiimpl.API; +import com.raoulvdberge.refinedstorage.apiimpl.network.node.GridNetworkNode; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.inventory.CraftingInventory; import net.minecraft.inventory.InventoryHelper; @@ -189,5 +190,10 @@ public class CraftingGridBehavior implements ICraftingGridBehavior { } } } + + if (grid.getGridType() == GridType.PATTERN) { + ((GridNetworkNode) grid).setProcessingPattern(false); + ((GridNetworkNode) grid).markDirty(); + } } } diff --git a/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/network/node/AllowedTags.java b/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/network/node/AllowedTags.java new file mode 100644 index 000000000..db4bed9be --- /dev/null +++ b/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/network/node/AllowedTags.java @@ -0,0 +1,114 @@ +package com.raoulvdberge.refinedstorage.apiimpl.network.node; + +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.ListNBT; +import net.minecraft.nbt.StringNBT; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.util.Constants; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class AllowedTags { + private static final String NBT_ALLOWED_ITEM_TAGS = "AllowedItemTags"; + private static final String NBT_ALLOWED_FLUID_TAGS = "AllowedFluidTags"; + + private List> allowedItemTags = new ArrayList<>(); + private List> allowedFluidTags = new ArrayList<>(); + + @Nullable + private final Runnable listener; + + public AllowedTags(@Nullable Runnable listener) { + for (int i = 0; i < 9; ++i) { + allowedItemTags.add(new HashSet<>()); + allowedFluidTags.add(new HashSet<>()); + } + + this.listener = listener; + } + + public CompoundNBT writeToNbt() { + CompoundNBT tag = new CompoundNBT(); + + tag.put(NBT_ALLOWED_ITEM_TAGS, getList(allowedItemTags)); + tag.put(NBT_ALLOWED_FLUID_TAGS, getList(allowedFluidTags)); + + return tag; + } + + public void readFromNbt(CompoundNBT tag) { + if (tag.contains(NBT_ALLOWED_ITEM_TAGS)) { + applyList(allowedItemTags, tag.getList(NBT_ALLOWED_ITEM_TAGS, Constants.NBT.TAG_LIST)); + } + + if (tag.contains(NBT_ALLOWED_FLUID_TAGS)) { + applyList(allowedFluidTags, tag.getList(NBT_ALLOWED_FLUID_TAGS, Constants.NBT.TAG_LIST)); + } + } + + private ListNBT getList(List> tagsPerSlot) { + ListNBT list = new ListNBT(); + + for (Set tags : tagsPerSlot) { + ListNBT subList = new ListNBT(); + + tags.forEach(t -> subList.add(new StringNBT(t.toString()))); + + list.add(subList); + } + + return list; + } + + private void applyList(List> list, ListNBT tagList) { + for (int i = 0; i < tagList.size(); ++i) { + ListNBT subList = tagList.getList(i); + + for (int j = 0; j < subList.size(); ++j) { + list.get(i).add(new ResourceLocation(subList.getString(j))); + } + } + } + + public List> getAllowedItemTags() { + return allowedItemTags; + } + + public List> getAllowedFluidTags() { + return allowedFluidTags; + } + + public void setAllowedItemTags(List> allowedItemTags) { + this.allowedItemTags = allowedItemTags; + + notifyListener(); + } + + public void setAllowedFluidTags(List> allowedFluidTags) { + this.allowedFluidTags = allowedFluidTags; + + notifyListener(); + } + + public void clearItemTags(int slot) { + this.allowedItemTags.get(slot).clear(); + + notifyListener(); + } + + public void clearFluidTags(int slot) { + this.allowedFluidTags.get(slot).clear(); + + notifyListener(); + } + + private void notifyListener() { + if (listener != null) { + listener.run(); + } + } +} diff --git a/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/network/node/GridNetworkNode.java b/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/network/node/GridNetworkNode.java index c1f5b161c..389d905e7 100644 --- a/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/network/node/GridNetworkNode.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/apiimpl/network/node/GridNetworkNode.java @@ -37,6 +37,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.ICraftingRecipe; import net.minecraft.item.crafting.IRecipeType; import net.minecraft.nbt.CompoundNBT; +import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.ITextComponent; @@ -73,6 +74,9 @@ public class GridNetworkNode extends NetworkNode implements INetworkAwareGrid, I private static final String NBT_PROCESSING_PATTERN = "ProcessingPattern"; private static final String NBT_PROCESSING_TYPE = "ProcessingType"; private static final String NBT_PROCESSING_MATRIX_FLUIDS = "ProcessingMatrixFluids"; + private static final String NBT_ALLOWED_TAGS = "AllowedTags"; + + private final AllowedTags allowedTags = new AllowedTags(this::updateAllowedTags); private Container craftingContainer = new Container(ContainerType.CRAFTING, 0) { @Override @@ -90,8 +94,20 @@ public class GridNetworkNode extends NetworkNode implements INetworkAwareGrid, I private ICraftingRecipe currentRecipe; private CraftingInventory matrix = new CraftingInventory(craftingContainer, 3, 3); private CraftResultInventory result = new CraftResultInventory(); - private BaseItemHandler processingMatrix = new BaseItemHandler(9 * 2).addListener(new NetworkNodeInventoryListener(this)); - private FluidInventory processingMatrixFluids = new FluidInventory(9 * 2, FluidAttributes.BUCKET_VOLUME * 64).addListener(new NetworkNodeFluidInventoryListener(this)); + private BaseItemHandler processingMatrix = new BaseItemHandler(9 * 2) + .addListener(new NetworkNodeInventoryListener(this)) + .addListener((handler, slot, reading) -> { + if (!reading && slot < 9) { + allowedTags.clearItemTags(slot); + } + }); + private FluidInventory processingMatrixFluids = new FluidInventory(9 * 2, FluidAttributes.BUCKET_VOLUME * 64) + .addListener(new NetworkNodeFluidInventoryListener(this)) + .addListener((handler, slot, reading) -> { + if (!reading && slot < 9) { + allowedTags.clearFluidTags(slot); + } + }); private boolean reading; @@ -124,9 +140,9 @@ public class GridNetworkNode extends NetworkNode implements INetworkAwareGrid, I ItemStack pattern = handler.getStackInSlot(slot); if (slot == 1 && !pattern.isEmpty()) { - boolean isPatternProcessing = PatternItem.isProcessing(pattern); + boolean processing = PatternItem.isProcessing(pattern); - if (isPatternProcessing && isProcessingPattern()) { + if (processing) { for (int i = 0; i < 9; ++i) { processingMatrix.setStackInSlot(i, PatternItem.getInputSlot(pattern, i)); processingMatrixFluids.setFluid(i, PatternItem.getFluidInputSlot(pattern, i)); @@ -136,11 +152,21 @@ public class GridNetworkNode extends NetworkNode implements INetworkAwareGrid, I processingMatrix.setStackInSlot(9 + i, PatternItem.getOutputSlot(pattern, i)); processingMatrixFluids.setFluid(9 + i, PatternItem.getFluidOutputSlot(pattern, i)); } - } else if (!isPatternProcessing && !isProcessingPattern()) { + + AllowedTags allowedTagsFromPattern = PatternItem.getAllowedTags(pattern); + + if (allowedTagsFromPattern != null) { + allowedTags.setAllowedItemTags(allowedTagsFromPattern.getAllowedItemTags()); + allowedTags.setAllowedFluidTags(allowedTagsFromPattern.getAllowedFluidTags()); + } + } else { for (int i = 0; i < 9; ++i) { matrix.setInventorySlotContents(i, PatternItem.getInputSlot(pattern, i)); } } + + setProcessingPattern(processing); + markDirty(); } })); @@ -169,6 +195,21 @@ public class GridNetworkNode extends NetworkNode implements INetworkAwareGrid, I this.type = type; } + public AllowedTags getAllowedTags() { + return allowedTags; + } + + private void updateAllowedTags() { + markDirty(); + + TileEntity tile = world.getTileEntity(pos); + + if (tile instanceof GridTile) { + ((GridTile) tile).getDataManager().sendParameterToWatchers(GridTile.ALLOWED_ITEM_TAGS); + ((GridTile) tile).getDataManager().sendParameterToWatchers(GridTile.ALLOWED_FLUID_TAGS); + } + } + @Override public int getEnergyUsage() { switch (type) { @@ -402,9 +443,14 @@ public class GridNetworkNode extends NetworkNode implements INetworkAwareGrid, I ItemStack pattern = new ItemStack(RSItems.PATTERN); PatternItem.setToCurrentVersion(pattern); - PatternItem.setExact(pattern, exactPattern); PatternItem.setProcessing(pattern, processingPattern); + if (!processingPattern) { + PatternItem.setExact(pattern, exactPattern); + } else { + PatternItem.setAllowedTags(pattern, allowedTags); + } + if (processingPattern) { for (int i = 0; i < 18; ++i) { if (!processingMatrix.getStackInSlot(i).isEmpty()) { @@ -580,6 +626,10 @@ public class GridNetworkNode extends NetworkNode implements INetworkAwareGrid, I public void read(CompoundNBT tag) { super.read(tag); + if (tag.contains(NBT_ALLOWED_TAGS)) { + allowedTags.readFromNbt(tag.getCompound(NBT_ALLOWED_TAGS)); + } + reading = true; StackUtils.readItems(matrix, 0, tag); @@ -611,6 +661,8 @@ public class GridNetworkNode extends NetworkNode implements INetworkAwareGrid, I public CompoundNBT write(CompoundNBT tag) { super.write(tag); + tag.put(NBT_ALLOWED_TAGS, allowedTags.writeToNbt()); + StackUtils.writeItems(matrix, 0, tag); StackUtils.writeItems(patterns, 1, tag); StackUtils.writeItems(filter, 2, tag); diff --git a/src/main/java/com/raoulvdberge/refinedstorage/container/GridContainer.java b/src/main/java/com/raoulvdberge/refinedstorage/container/GridContainer.java index e1c95a10a..c4ce21e94 100644 --- a/src/main/java/com/raoulvdberge/refinedstorage/container/GridContainer.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/container/GridContainer.java @@ -169,8 +169,18 @@ public class GridContainer extends BaseContainer implements ICraftingGridListene int y = headerAndSlots + 4; for (int i = 0; i < 9 * 2; ++i) { - addSlot(new FilterSlot(((GridNetworkNode) grid).getProcessingMatrix(), i, x, y, FilterSlot.FILTER_ALLOW_SIZE).setEnableHandler(() -> ((GridNetworkNode) grid).isProcessingPattern() && ((GridNetworkNode) grid).getType() == IType.ITEMS)); - addSlot(new FluidFilterSlot(((GridNetworkNode) grid).getProcessingMatrixFluids(), i, x, y, FilterSlot.FILTER_ALLOW_SIZE).setEnableHandler(() -> ((GridNetworkNode) grid).isProcessingPattern() && ((GridNetworkNode) grid).getType() == IType.FLUIDS)); + int itemFilterSlotConfig = FilterSlot.FILTER_ALLOW_SIZE; + if (i < 9) { + itemFilterSlotConfig |= FilterSlot.FILTER_ALLOW_INPUT_CONFIGURATION; + } + + int fluidFilterSlotConfig = FluidFilterSlot.FILTER_ALLOW_SIZE; + if (i < 9) { + fluidFilterSlotConfig |= FluidFilterSlot.FILTER_ALLOW_INPUT_CONFIGURATION; + } + + addSlot(new FilterSlot(((GridNetworkNode) grid).getProcessingMatrix(), i, x, y, itemFilterSlotConfig).setEnableHandler(() -> ((GridNetworkNode) grid).isProcessingPattern() && ((GridNetworkNode) grid).getType() == IType.ITEMS)); + addSlot(new FluidFilterSlot(((GridNetworkNode) grid).getProcessingMatrixFluids(), i, x, y, fluidFilterSlotConfig).setEnableHandler(() -> ((GridNetworkNode) grid).isProcessingPattern() && ((GridNetworkNode) grid).getType() == IType.FLUIDS)); x += 18; diff --git a/src/main/java/com/raoulvdberge/refinedstorage/container/InputConfigurationContainer.java b/src/main/java/com/raoulvdberge/refinedstorage/container/InputConfigurationContainer.java new file mode 100644 index 000000000..a392089aa --- /dev/null +++ b/src/main/java/com/raoulvdberge/refinedstorage/container/InputConfigurationContainer.java @@ -0,0 +1,9 @@ +package com.raoulvdberge.refinedstorage.container; + +import net.minecraft.entity.player.PlayerEntity; + +public class InputConfigurationContainer extends BaseContainer { + public InputConfigurationContainer(PlayerEntity player) { + super(null, null, player, 0); + } +} diff --git a/src/main/java/com/raoulvdberge/refinedstorage/container/slot/filter/FilterSlot.java b/src/main/java/com/raoulvdberge/refinedstorage/container/slot/filter/FilterSlot.java index 8cce26079..4b738791e 100644 --- a/src/main/java/com/raoulvdberge/refinedstorage/container/slot/filter/FilterSlot.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/container/slot/filter/FilterSlot.java @@ -10,8 +10,9 @@ import javax.annotation.Nonnull; public class FilterSlot extends BaseSlot { public static final int FILTER_ALLOW_SIZE = 1; public static final int FILTER_ALLOW_BLOCKS = 2; + public static final int FILTER_ALLOW_INPUT_CONFIGURATION = 4; - private int flags = 0; + private int flags; public FilterSlot(IItemHandler handler, int inventoryIndex, int x, int y, int flags) { super(handler, inventoryIndex, x, y); @@ -52,4 +53,8 @@ public class FilterSlot extends BaseSlot { public boolean isBlockAllowed() { return (flags & FILTER_ALLOW_BLOCKS) == FILTER_ALLOW_BLOCKS; } + + public boolean isInputConfigurationAllowed() { + return (flags & FILTER_ALLOW_INPUT_CONFIGURATION) == FILTER_ALLOW_INPUT_CONFIGURATION; + } } diff --git a/src/main/java/com/raoulvdberge/refinedstorage/container/slot/filter/FluidFilterSlot.java b/src/main/java/com/raoulvdberge/refinedstorage/container/slot/filter/FluidFilterSlot.java index 0e23641a8..6caa4e5a8 100644 --- a/src/main/java/com/raoulvdberge/refinedstorage/container/slot/filter/FluidFilterSlot.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/container/slot/filter/FluidFilterSlot.java @@ -10,6 +10,7 @@ import javax.annotation.Nonnull; public class FluidFilterSlot extends BaseSlot { public static final int FILTER_ALLOW_SIZE = 1; + public static final int FILTER_ALLOW_INPUT_CONFIGURATION = 2; private int flags; private FluidInventory fluidInventory; @@ -38,6 +39,10 @@ public class FluidFilterSlot extends BaseSlot { return (flags & FILTER_ALLOW_SIZE) == FILTER_ALLOW_SIZE; } + public boolean isInputConfigurationAllowed() { + return (flags & FILTER_ALLOW_INPUT_CONFIGURATION) == FILTER_ALLOW_INPUT_CONFIGURATION; + } + public FluidInventory getFluidInventory() { return fluidInventory; } diff --git a/src/main/java/com/raoulvdberge/refinedstorage/integration/jei/GridRecipeTransferHandler.java b/src/main/java/com/raoulvdberge/refinedstorage/integration/jei/GridRecipeTransferHandler.java index 0cfdf0ea0..f5b74b73a 100644 --- a/src/main/java/com/raoulvdberge/refinedstorage/integration/jei/GridRecipeTransferHandler.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/integration/jei/GridRecipeTransferHandler.java @@ -3,13 +3,13 @@ package com.raoulvdberge.refinedstorage.integration.jei; import com.raoulvdberge.refinedstorage.RS; import com.raoulvdberge.refinedstorage.api.network.grid.GridType; import com.raoulvdberge.refinedstorage.api.network.grid.IGrid; -import com.raoulvdberge.refinedstorage.apiimpl.network.node.GridNetworkNode; import com.raoulvdberge.refinedstorage.container.GridContainer; import com.raoulvdberge.refinedstorage.network.grid.GridProcessingTransferMessage; import com.raoulvdberge.refinedstorage.network.grid.GridTransferMessage; import mezz.jei.api.constants.VanillaRecipeCategoryUid; import mezz.jei.api.gui.IRecipeLayout; import mezz.jei.api.gui.ingredient.IGuiIngredient; +import mezz.jei.api.recipe.category.IRecipeCategory; import mezz.jei.api.recipe.transfer.IRecipeTransferError; import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; import net.minecraft.entity.player.PlayerEntity; @@ -26,18 +26,6 @@ public class GridRecipeTransferHandler implements IRecipeTransferHandler { public static final long TRANSFER_SCROLLBAR_DELAY_MS = 200; public static long LAST_TRANSFER_TIME; - private static final IRecipeTransferError ERROR_CANNOT_TRANSFER = new IRecipeTransferError() { - @Override - public Type getType() { - return Type.INTERNAL; - } - - @Override - public void showError(int i, int i1, IRecipeLayout iRecipeLayout, int i2, int i3) { - - } - }; - @Override public Class getContainerClass() { return GridContainer.class; @@ -50,7 +38,7 @@ public class GridRecipeTransferHandler implements IRecipeTransferHandler { if (doTransfer) { LAST_TRANSFER_TIME = System.currentTimeMillis(); - if (grid.getGridType() == GridType.PATTERN && ((GridNetworkNode) grid).isProcessingPattern()) { + if (grid.getGridType() == GridType.PATTERN && !isCraftingRecipe(recipeLayout.getRecipeCategory())) { List inputs = new LinkedList<>(); List outputs = new LinkedList<>(); @@ -88,18 +76,12 @@ public class GridRecipeTransferHandler implements IRecipeTransferHandler { container.inventorySlots.stream().filter(s -> s.inventory instanceof CraftingInventory).collect(Collectors.toList()) )); } - } else { - if (grid.getGridType() == GridType.PATTERN && ((GridNetworkNode) grid).isProcessingPattern()) { - if (recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) { - return ERROR_CANNOT_TRANSFER; - } - } else { - if (!recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) { - return ERROR_CANNOT_TRANSFER; - } - } } return null; } + + private boolean isCraftingRecipe(IRecipeCategory recipeCategory) { + return recipeCategory.getUid().equals(VanillaRecipeCategoryUid.CRAFTING); + } } diff --git a/src/main/java/com/raoulvdberge/refinedstorage/item/PatternItem.java b/src/main/java/com/raoulvdberge/refinedstorage/item/PatternItem.java index a44946964..3979acae3 100644 --- a/src/main/java/com/raoulvdberge/refinedstorage/item/PatternItem.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/item/PatternItem.java @@ -6,6 +6,8 @@ import com.raoulvdberge.refinedstorage.api.autocrafting.ICraftingPattern; import com.raoulvdberge.refinedstorage.api.autocrafting.ICraftingPatternContainer; import com.raoulvdberge.refinedstorage.api.autocrafting.ICraftingPatternProvider; import com.raoulvdberge.refinedstorage.apiimpl.autocrafting.CraftingPattern; +import com.raoulvdberge.refinedstorage.apiimpl.autocrafting.CraftingPatternFactory; +import com.raoulvdberge.refinedstorage.apiimpl.network.node.AllowedTags; import com.raoulvdberge.refinedstorage.render.tesr.PatternItemStackTileRenderer; import com.raoulvdberge.refinedstorage.util.RenderUtils; import net.minecraft.client.gui.screen.inventory.ContainerScreen; @@ -17,6 +19,7 @@ import net.minecraft.nbt.CompoundNBT; import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResultType; import net.minecraft.util.Hand; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.Style; import net.minecraft.util.text.TextFormatting; @@ -29,6 +32,7 @@ import javax.annotation.Nullable; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; public class PatternItem extends Item implements ICraftingPatternProvider { @@ -41,6 +45,7 @@ public class PatternItem extends Item implements ICraftingPatternProvider { private static final String NBT_FLUID_OUTPUT_SLOT = "FluidOutput_%d"; private static final String NBT_EXACT = "Exact"; private static final String NBT_PROCESSING = "Processing"; + private static final String NBT_ALLOWED_TAGS = "AllowedTags"; private static final int VERSION = 1; @@ -52,7 +57,7 @@ public class PatternItem extends Item implements ICraftingPatternProvider { public static CraftingPattern fromCache(World world, ItemStack stack) { if (!CACHE.containsKey(stack)) { - CACHE.put(stack, new CraftingPattern(world, null, stack)); + CACHE.put(stack, CraftingPatternFactory.INSTANCE.create(world, null, stack)); } return CACHE.get(stack); @@ -66,10 +71,11 @@ public class PatternItem extends Item implements ICraftingPatternProvider { return; } - ICraftingPattern pattern = fromCache(world, stack); + CraftingPattern pattern = fromCache(world, stack); Style yellow = new Style().setColor(TextFormatting.YELLOW); Style blue = new Style().setColor(TextFormatting.BLUE); + Style aqua = new Style().setColor(TextFormatting.AQUA); Style red = new Style().setColor(TextFormatting.RED); if (pattern.isValid()) { @@ -85,6 +91,32 @@ public class PatternItem extends Item implements ICraftingPatternProvider { RenderUtils.addCombinedItemsToTooltip(tooltip, true, pattern.getOutputs()); RenderUtils.addCombinedFluidsToTooltip(tooltip, true, pattern.getFluidOutputs()); + if (pattern.getAllowedTags() != null) { + for (int i = 0; i < pattern.getAllowedTags().getAllowedItemTags().size(); ++i) { + Set allowedTags = pattern.getAllowedTags().getAllowedItemTags().get(i); + + for (ResourceLocation tag : allowedTags) { + tooltip.add(new TranslationTextComponent( + "misc.refinedstorage.pattern.allowed_item_tag", + tag.toString(), + pattern.getInputs().get(i).get(0).getDisplayName() + ).setStyle(aqua)); + } + } + + for (int i = 0; i < pattern.getAllowedTags().getAllowedFluidTags().size(); ++i) { + Set allowedTags = pattern.getAllowedTags().getAllowedFluidTags().get(i); + + for (ResourceLocation tag : allowedTags) { + tooltip.add(new TranslationTextComponent( + "misc.refinedstorage.pattern.allowed_fluid_tag", + tag.toString(), + pattern.getFluidInputs().get(i).get(0).getDisplayName() + ).setStyle(aqua)); + } + } + } + if (isExact(stack)) { tooltip.add(new TranslationTextComponent("misc.refinedstorage.pattern.exact").setStyle(blue)); } @@ -94,6 +126,7 @@ public class PatternItem extends Item implements ICraftingPatternProvider { } } else { tooltip.add(new TranslationTextComponent("misc.refinedstorage.pattern.invalid").setStyle(red)); + tooltip.add(pattern.getErrorMessage().setStyle(new Style().setColor(TextFormatting.GRAY))); } } @@ -109,7 +142,7 @@ public class PatternItem extends Item implements ICraftingPatternProvider { @Override @Nonnull public ICraftingPattern create(World world, ItemStack stack, ICraftingPatternContainer container) { - return new CraftingPattern(world, container, stack); + return CraftingPatternFactory.INSTANCE.create(world, container, stack); } public static void setInputSlot(ItemStack pattern, int slot, ItemStack stack) { @@ -200,7 +233,7 @@ public class PatternItem extends Item implements ICraftingPatternProvider { public static boolean isExact(ItemStack pattern) { if (!pattern.hasTag() || !pattern.getTag().contains(NBT_EXACT)) { - return true; + return false; } return pattern.getTag().getBoolean(NBT_EXACT); @@ -221,4 +254,25 @@ public class PatternItem extends Item implements ICraftingPatternProvider { pattern.getTag().putInt(NBT_VERSION, VERSION); } + + public static void setAllowedTags(ItemStack pattern, AllowedTags allowedTags) { + if (!pattern.hasTag()) { + pattern.setTag(new CompoundNBT()); + } + + pattern.getTag().put(NBT_ALLOWED_TAGS, allowedTags.writeToNbt()); + } + + @Nullable + public static AllowedTags getAllowedTags(ItemStack pattern) { + if (!pattern.hasTag() || !pattern.getTag().contains(NBT_ALLOWED_TAGS)) { + return null; + } + + AllowedTags allowedTags = new AllowedTags(null); + + allowedTags.readFromNbt(pattern.getTag().getCompound(NBT_ALLOWED_TAGS)); + + return allowedTags; + } } diff --git a/src/main/java/com/raoulvdberge/refinedstorage/network/grid/GridProcessingTransferMessage.java b/src/main/java/com/raoulvdberge/refinedstorage/network/grid/GridProcessingTransferMessage.java index 6aa9025dc..7d48d286e 100644 --- a/src/main/java/com/raoulvdberge/refinedstorage/network/grid/GridProcessingTransferMessage.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/network/grid/GridProcessingTransferMessage.java @@ -113,6 +113,9 @@ public class GridProcessingTransferMessage { setFluidInputs(handlerFluid, message.fluidInputs); setFluidOutputs(handlerFluid, message.fluidOutputs); + + ((GridNetworkNode) grid).setProcessingPattern(true); + ((GridNetworkNode) grid).markDirty(); } } }); diff --git a/src/main/java/com/raoulvdberge/refinedstorage/screen/BaseScreen.java b/src/main/java/com/raoulvdberge/refinedstorage/screen/BaseScreen.java index 8de87b4db..b90f3996e 100644 --- a/src/main/java/com/raoulvdberge/refinedstorage/screen/BaseScreen.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/screen/BaseScreen.java @@ -7,6 +7,7 @@ import com.raoulvdberge.refinedstorage.container.slot.filter.FilterSlot; import com.raoulvdberge.refinedstorage.container.slot.filter.FluidFilterSlot; import com.raoulvdberge.refinedstorage.integration.craftingtweaks.CraftingTweaksIntegration; import com.raoulvdberge.refinedstorage.render.FluidRenderer; +import com.raoulvdberge.refinedstorage.screen.grid.InputConfigurationScreen; import com.raoulvdberge.refinedstorage.screen.widget.CheckBoxWidget; import com.raoulvdberge.refinedstorage.screen.widget.sidebutton.SideButton; import com.raoulvdberge.refinedstorage.util.RenderUtils; @@ -23,8 +24,8 @@ import net.minecraft.inventory.container.Slot; import net.minecraft.item.ItemStack; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; import net.minecraftforge.fluids.FluidStack; -import net.minecraftforge.fml.client.config.GuiCheckBox; import net.minecraftforge.fml.client.config.GuiUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -175,25 +176,45 @@ public abstract class BaseScreen extends ContainerScreen if (valid && slot instanceof FilterSlot && slot.isEnabled() && ((FilterSlot) slot).isSizeAllowed()) { if (!slot.getStack().isEmpty()) { - minecraft.displayGuiScreen(new AmountScreen( - this, - minecraft.player, - slot.slotNumber, - slot.getStack(), - slot.getSlotStackLimit() - )); + if (((FilterSlot) slot).isInputConfigurationAllowed() && hasControlDown()) { + minecraft.displayGuiScreen(new InputConfigurationScreen( + this, + minecraft.player, + new TranslationTextComponent("gui.refinedstorage.input_configuration"), + slot.getStack(), + slot.getSlotIndex() + )); + } else { + minecraft.displayGuiScreen(new AmountScreen( + this, + minecraft.player, + slot.slotNumber, + slot.getStack(), + slot.getSlotStackLimit() + )); + } } } else if (valid && slot instanceof FluidFilterSlot && slot.isEnabled() && ((FluidFilterSlot) slot).isSizeAllowed()) { FluidStack stack = ((FluidFilterSlot) slot).getFluidInventory().getFluid(slot.getSlotIndex()); if (!stack.isEmpty()) { - minecraft.displayGuiScreen(new FluidAmountScreen( - this, - minecraft.player, - slot.slotNumber, - stack, - ((FluidFilterSlot) slot).getFluidInventory().getMaxAmount() - )); + if (((FluidFilterSlot) slot).isInputConfigurationAllowed() && hasControlDown()) { + minecraft.displayGuiScreen(new InputConfigurationScreen( + this, + minecraft.player, + new TranslationTextComponent("gui.refinedstorage.input_configuration"), + stack, + slot.getSlotIndex() + )); + } else { + minecraft.displayGuiScreen(new FluidAmountScreen( + this, + minecraft.player, + slot.slotNumber, + stack, + ((FluidFilterSlot) slot).getFluidInventory().getMaxAmount() + )); + } } else { super.handleMouseClick(slot, slotId, mouseButton, type); } @@ -202,7 +223,7 @@ public abstract class BaseScreen extends ContainerScreen } } - public GuiCheckBox addCheckBox(int x, int y, String text, boolean checked, Button.IPressable onPress) { + public CheckBoxWidget addCheckBox(int x, int y, String text, boolean checked, Button.IPressable onPress) { CheckBoxWidget checkBox = new CheckBoxWidget(x, y, text, checked, onPress); this.addButton(checkBox); diff --git a/src/main/java/com/raoulvdberge/refinedstorage/screen/grid/GridScreen.java b/src/main/java/com/raoulvdberge/refinedstorage/screen/grid/GridScreen.java index d1b53c65a..2ff9e190e 100644 --- a/src/main/java/com/raoulvdberge/refinedstorage/screen/grid/GridScreen.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/screen/grid/GridScreen.java @@ -134,8 +134,10 @@ public class GridScreen extends BaseScreen implements IScreenInfo TileDataManager.setParameter(GridTile.PROCESSING_PATTERN, processingPattern.isChecked()); }); - exactPattern = addCheckBox(processingPattern.x + processingPattern.getWidth() + 5, y + getTopHeight() + (getVisibleRows() * 18) + 60, I18n.format("misc.refinedstorage.exact"), GridTile.EXACT_PATTERN.getValue(), btn -> TileDataManager.setParameter(GridTile.EXACT_PATTERN, exactPattern.isChecked())); - + if (!processingPattern.isChecked()) { + exactPattern = addCheckBox(processingPattern.x + processingPattern.getWidth() + 5, y + getTopHeight() + (getVisibleRows() * 18) + 60, I18n.format("misc.refinedstorage.exact"), GridTile.EXACT_PATTERN.getValue(), btn -> TileDataManager.setParameter(GridTile.EXACT_PATTERN, exactPattern.isChecked())); + } + addSideButton(new TypeSideButton(this, GridTile.PROCESSING_TYPE)); } diff --git a/src/main/java/com/raoulvdberge/refinedstorage/screen/grid/InputConfigurationScreen.java b/src/main/java/com/raoulvdberge/refinedstorage/screen/grid/InputConfigurationScreen.java new file mode 100644 index 000000000..0850e35f7 --- /dev/null +++ b/src/main/java/com/raoulvdberge/refinedstorage/screen/grid/InputConfigurationScreen.java @@ -0,0 +1,385 @@ +package com.raoulvdberge.refinedstorage.screen.grid; + +import com.raoulvdberge.refinedstorage.RS; +import com.raoulvdberge.refinedstorage.container.InputConfigurationContainer; +import com.raoulvdberge.refinedstorage.render.FluidRenderer; +import com.raoulvdberge.refinedstorage.screen.BaseScreen; +import com.raoulvdberge.refinedstorage.screen.widget.CheckBoxWidget; +import com.raoulvdberge.refinedstorage.screen.widget.ScrollbarWidget; +import com.raoulvdberge.refinedstorage.tile.config.IType; +import com.raoulvdberge.refinedstorage.tile.data.TileDataManager; +import com.raoulvdberge.refinedstorage.tile.grid.GridTile; +import com.raoulvdberge.refinedstorage.util.RenderUtils; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.button.Button; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.fluid.Fluid; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.tags.FluidTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; +import net.minecraftforge.fluids.FluidAttributes; +import net.minecraftforge.fluids.FluidStack; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class InputConfigurationScreen extends BaseScreen { + private final Screen parent; + private final ScrollbarWidget scrollbar; + + private final List lines = new ArrayList<>(); + + private int type; + private int slot; + private ItemStack item; + private FluidStack fluid; + + private InputConfigurationScreen(Screen parent, PlayerEntity player, ITextComponent title) { + super(new InputConfigurationContainer(player), 175, 143, null, title); + + this.parent = parent; + this.scrollbar = new ScrollbarWidget(this, 155, 20, 12, 89); + } + + public InputConfigurationScreen(Screen parent, PlayerEntity player, ITextComponent title, ItemStack item, int slot) { + this(parent, player, title); + + this.type = IType.ITEMS; + this.slot = slot; + this.item = item; + this.fluid = null; + } + + public InputConfigurationScreen(Screen parent, PlayerEntity player, ITextComponent title, FluidStack fluid, int slot) { + this(parent, player, title); + + this.type = IType.FLUIDS; + this.slot = slot; + this.item = null; + this.fluid = fluid; + } + + @Override + public void onPostInit(int x, int y) { + Button apply = addButton(x + 7, y + 114, 50, 20, I18n.format("gui.refinedstorage.input_configuration.apply"), true, true, btn -> apply()); + addButton(x + apply.getWidth() + 7 + 4, y + 114, 50, 20, I18n.format("gui.cancel"), true, true, btn -> close()); + + lines.clear(); + + if (item != null) { + lines.add(new ItemLine(item)); + + for (ResourceLocation owningTag : ItemTags.getCollection().getOwningTags(item.getItem())) { + lines.add(new TagLine(owningTag, GridTile.ALLOWED_ITEM_TAGS.getValue().get(slot).contains(owningTag))); + + int itemCount = 0; + + ItemListLine line = new ItemListLine(); + + for (Item item : ItemTags.getCollection().get(owningTag).getAllElements()) { + if (itemCount > 0 && itemCount % 7 == 0) { + lines.add(line); + line = new ItemListLine(); + } + + itemCount++; + + line.addItem(new ItemStack(item)); + } + + lines.add(line); + } + } else if (fluid != null) { + lines.add(new FluidLine(fluid)); + + for (ResourceLocation owningTag : FluidTags.getCollection().getOwningTags(fluid.getFluid())) { + lines.add(new TagLine(owningTag, GridTile.ALLOWED_FLUID_TAGS.getValue().get(slot).contains(owningTag))); + + int fluidCount = 0; + + FluidListLine line = new FluidListLine(); + + for (Fluid fluid : FluidTags.getCollection().get(owningTag).getAllElements()) { + if (fluidCount > 0 && fluidCount % 7 == 0) { + lines.add(line); + line = new FluidListLine(); + } + + fluidCount++; + + line.addFluid(new FluidStack(fluid, FluidAttributes.BUCKET_VOLUME)); + } + + lines.add(line); + } + } + + // Do an initial layout + int xx = 8; + int yy = 20; + + for (int i = 0; i < lines.size(); ++i) { + boolean visible = i >= scrollbar.getOffset() && i < scrollbar.getOffset() + getVisibleRows(); + + if (visible) { + lines.get(i).layoutDependantControls(true, guiLeft + xx + 3, guiTop + yy + 3); + + yy += 18; + } + } + } + + @Override + public void tick(int x, int y) { + scrollbar.setEnabled(getRows() > getVisibleRows()); + scrollbar.setMaxOffset(getRows() - getVisibleRows()); + } + + private int getRows() { + return lines.size(); + } + + private int getVisibleRows() { + return 5; + } + + @Override + public void renderBackground(int x, int y, int mouseX, int mouseY) { + bindTexture(RS.ID, "gui/input_configuration.png"); + + blit(x, y, 0, 0, xSize, ySize); + + scrollbar.render(); + } + + @Override + public void renderForeground(int mouseX, int mouseY) { + renderString(7, 7, title.getFormattedText()); + + int x = 8; + int y = 20; + + RenderHelper.enableGUIStandardItemLighting(); + + for (int i = 0; i < lines.size(); ++i) { + boolean visible = i >= scrollbar.getOffset() && i < scrollbar.getOffset() + getVisibleRows(); + + if (visible) { + lines.get(i).layoutDependantControls(true, guiLeft + x + 3, guiTop + y + 3); + lines.get(i).render(x, y); + + y += 18; + } else { + lines.get(i).layoutDependantControls(false, -100, -100); + } + } + + x = 8; + y = 20; + + for (int i = 0; i < lines.size(); ++i) { + boolean visible = i >= scrollbar.getOffset() && i < scrollbar.getOffset() + getVisibleRows(); + + if (visible) { + lines.get(i).renderTooltip(x, y, mouseX, mouseY); + + y += 18; + } + } + } + + @Override + public void mouseMoved(double mx, double my) { + scrollbar.mouseMoved(mx, my); + + super.mouseMoved(mx, my); + } + + @Override + public boolean mouseClicked(double mx, double my, int button) { + return scrollbar.mouseClicked(mx, my, button) || super.mouseClicked(mx, my, button); + } + + @Override + public boolean mouseReleased(double mx, double my, int button) { + return scrollbar.mouseReleased(mx, my, button) || super.mouseReleased(mx, my, button); + } + + @Override + public boolean mouseScrolled(double x, double y, double delta) { + return this.scrollbar.mouseScrolled(x, y, delta) || super.mouseScrolled(x, y, delta); + } + + @Override + public boolean keyPressed(int key, int scanCode, int modifiers) { + if (key == GLFW.GLFW_KEY_ESCAPE) { + close(); + + return true; + } + + return super.keyPressed(key, scanCode, modifiers); + } + + private void close() { + minecraft.displayGuiScreen(parent); + } + + private void apply() { + Set allowed = new HashSet<>(); + + for (Line line : lines) { + if (line instanceof TagLine) { + TagLine tagLine = (TagLine) line; + + if (tagLine.widget.isChecked()) { + allowed.add(tagLine.tagName); + } + } + } + + if (type == IType.ITEMS) { + List> existing = GridTile.ALLOWED_ITEM_TAGS.getValue(); + + existing.set(slot, allowed); + + TileDataManager.setParameter(GridTile.ALLOWED_ITEM_TAGS, existing); + } else if (type == IType.FLUIDS) { + List> existing = GridTile.ALLOWED_FLUID_TAGS.getValue(); + + existing.set(slot, allowed); + + TileDataManager.setParameter(GridTile.ALLOWED_FLUID_TAGS, existing); + } + + close(); + } + + private interface Line { + default void render(int x, int y) { + } + + default void renderTooltip(int x, int y, int mx, int my) { + } + + default void layoutDependantControls(boolean visible, int x, int y) { + } + } + + private class ItemLine implements Line { + private final ItemStack item; + + public ItemLine(ItemStack item) { + this.item = item; + } + + @Override + public void render(int x, int y) { + renderItem(x + 3, y + 2, item); + renderString(x + 4 + 19, y + 7, item.getDisplayName().getFormattedText()); + } + } + + private class FluidLine implements Line { + private final FluidStack fluid; + + public FluidLine(FluidStack item) { + this.fluid = item; + } + + @Override + public void render(int x, int y) { + FluidRenderer.INSTANCE.render(x + 3, y + 2, fluid); + renderString(x + 4 + 19, y + 7, fluid.getDisplayName().getFormattedText()); + } + } + + private class TagLine implements Line { + private final ResourceLocation tagName; + private final CheckBoxWidget widget; + + public TagLine(ResourceLocation tagName, boolean checked) { + this.tagName = tagName; + this.widget = addCheckBox(-100, -100, RenderUtils.shorten(tagName.toString(), 22), checked, (btn) -> { + }); + + widget.setFGColor(0xFF373737); + widget.setShadow(false); + } + + @Override + public void layoutDependantControls(boolean visible, int x, int y) { + widget.visible = visible; + widget.x = x; + widget.y = y; + } + } + + private class ItemListLine implements Line { + private final List items = new ArrayList<>(); + + public ItemListLine addItem(ItemStack stack) { + items.add(stack); + + return this; + } + + @Override + public void render(int x, int y) { + for (ItemStack item : items) { + renderItem(x + 3, y, item); + + x += 18; + } + } + + @Override + public void renderTooltip(int x, int y, int mx, int my) { + for (ItemStack item : items) { + if (RenderUtils.inBounds(x + 3, y, 16, 16, mx, my)) { + InputConfigurationScreen.this.renderTooltip(item, mx, my, RenderUtils.getTooltipFromItem(item)); + } + + x += 18; + } + } + } + + private class FluidListLine implements Line { + private final List fluids = new ArrayList<>(); + + public FluidListLine addFluid(FluidStack stack) { + fluids.add(stack); + + return this; + } + + @Override + public void render(int x, int y) { + for (FluidStack fluid : fluids) { + FluidRenderer.INSTANCE.render(x + 3, y, fluid); + + x += 18; + } + } + + @Override + public void renderTooltip(int x, int y, int mx, int my) { + for (FluidStack fluid : fluids) { + if (RenderUtils.inBounds(x + 3, y, 16, 16, mx, my)) { + InputConfigurationScreen.this.renderTooltip(mx, my, fluid.getDisplayName().getFormattedText()); + } + + x += 18; + } + } + } +} diff --git a/src/main/java/com/raoulvdberge/refinedstorage/screen/widget/CheckBoxWidget.java b/src/main/java/com/raoulvdberge/refinedstorage/screen/widget/CheckBoxWidget.java index 3ee0faeca..093d7ff5f 100644 --- a/src/main/java/com/raoulvdberge/refinedstorage/screen/widget/CheckBoxWidget.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/screen/widget/CheckBoxWidget.java @@ -10,6 +10,7 @@ public class CheckBoxWidget extends GuiCheckBox { private IPressable onPress; private String displayString; + private boolean shadow = true; public CheckBoxWidget(int xPos, int yPos, String displayString, boolean isChecked, IPressable onPress) { super(xPos, yPos, displayString, isChecked); @@ -19,6 +20,10 @@ public class CheckBoxWidget extends GuiCheckBox { this.width = Minecraft.getInstance().fontRenderer.getStringWidth(displayString) + BOX_WIDTH + 3; } + public void setShadow(boolean shadow) { + this.shadow = shadow; + } + @Override public void renderButton(int mouseX, int mouseY, float partial) { if (visible) { @@ -26,17 +31,21 @@ public class CheckBoxWidget extends GuiCheckBox { int color = 14737632; - if (packedFGColor != 0) { - color = packedFGColor; - } else if (!active) { + if (!active) { color = 10526880; + } else if (packedFGColor != 0) { + color = packedFGColor; } if (isChecked()) { drawCenteredString(Minecraft.getInstance().fontRenderer, "x", x + BOX_WIDTH / 2 + 1, y + 1, 14737632); } - drawString(Minecraft.getInstance().fontRenderer, displayString, x + BOX_WIDTH + 2, y + 2, color); + if (!shadow) { + Minecraft.getInstance().fontRenderer.drawString(displayString, x + BOX_WIDTH + 2, y + 2, color); + } else { + Minecraft.getInstance().fontRenderer.drawStringWithShadow(displayString, x + BOX_WIDTH + 2, y + 2, color); + } } } diff --git a/src/main/java/com/raoulvdberge/refinedstorage/tile/data/RSSerializers.java b/src/main/java/com/raoulvdberge/refinedstorage/tile/data/RSSerializers.java index 42acd5f59..a2cdd2cf0 100755 --- a/src/main/java/com/raoulvdberge/refinedstorage/tile/data/RSSerializers.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/tile/data/RSSerializers.java @@ -9,9 +9,7 @@ import net.minecraft.network.datasync.IDataSerializer; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fluids.FluidStack; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; public final class RSSerializers { public static final IDataSerializer> CLIENT_NODE_SERIALIZER = new IDataSerializer>() { @@ -143,4 +141,47 @@ public final class RSSerializers { return value; } }; + + public static final IDataSerializer>> LIST_OF_SET_SERIALIZER = new IDataSerializer>>() { + @Override + public void write(PacketBuffer buf, List> value) { + buf.writeInt(value.size()); + + for (Set values : value) { + buf.writeInt(values.size()); + + values.forEach(buf::writeResourceLocation); + } + } + + @Override + public List> read(PacketBuffer buf) { + List> value = new ArrayList<>(); + + int size = buf.readInt(); + for (int i = 0; i < size; ++i) { + int setSize = buf.readInt(); + + Set values = new HashSet<>(); + + for (int j = 0; j < setSize; ++j) { + values.add(buf.readResourceLocation()); + } + + value.add(values); + } + + return value; + } + + @Override + public DataParameter>> createKey(int id) { + return null; + } + + @Override + public List> copyValue(List> value) { + return value; + } + }; } diff --git a/src/main/java/com/raoulvdberge/refinedstorage/tile/grid/GridTile.java b/src/main/java/com/raoulvdberge/refinedstorage/tile/grid/GridTile.java index 02010e690..e0ab174da 100644 --- a/src/main/java/com/raoulvdberge/refinedstorage/tile/grid/GridTile.java +++ b/src/main/java/com/raoulvdberge/refinedstorage/tile/grid/GridTile.java @@ -7,10 +7,12 @@ import com.raoulvdberge.refinedstorage.screen.BaseScreen; import com.raoulvdberge.refinedstorage.screen.grid.GridScreen; import com.raoulvdberge.refinedstorage.tile.NetworkNodeTile; import com.raoulvdberge.refinedstorage.tile.config.IType; +import com.raoulvdberge.refinedstorage.tile.data.RSSerializers; import com.raoulvdberge.refinedstorage.tile.data.TileDataParameter; import com.raoulvdberge.refinedstorage.util.GridUtils; import net.minecraft.network.datasync.DataSerializers; import net.minecraft.util.Direction; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.common.capabilities.Capability; @@ -20,6 +22,9 @@ import net.minecraftforge.items.IItemHandler; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; public class GridTile extends NetworkNodeTile { public static final TileDataParameter VIEW_TYPE = new TileDataParameter<>(DataSerializers.VARINT, 0, t -> t.getNode().getViewType(), (t, v) -> { @@ -73,6 +78,14 @@ public class GridTile extends NetworkNodeTile { }, (initial, p) -> BaseScreen.executeLater(GridScreen.class, BaseScreen::init)); public static final TileDataParameter PROCESSING_TYPE = IType.createParameter((initial, p) -> BaseScreen.executeLater(GridScreen.class, BaseScreen::init)); + public static final TileDataParameter>, GridTile> ALLOWED_ITEM_TAGS = new TileDataParameter<>(RSSerializers.LIST_OF_SET_SERIALIZER, new ArrayList<>(), t -> t.getNode().getAllowedTags().getAllowedItemTags(), (t, v) -> { + t.getNode().getAllowedTags().setAllowedItemTags(v); + }); + + public static final TileDataParameter>, GridTile> ALLOWED_FLUID_TAGS = new TileDataParameter<>(RSSerializers.LIST_OF_SET_SERIALIZER, new ArrayList<>(), t -> t.getNode().getAllowedTags().getAllowedFluidTags(), (t, v) -> { + t.getNode().getAllowedTags().setAllowedFluidTags(v); + }); + public static void trySortGrid(boolean initial) { if (!initial) { BaseScreen.executeLater(GridScreen.class, grid -> grid.getView().sort()); @@ -98,6 +111,8 @@ public class GridTile extends NetworkNodeTile { dataManager.addWatchedParameter(EXACT_PATTERN); dataManager.addWatchedParameter(PROCESSING_PATTERN); dataManager.addWatchedParameter(PROCESSING_TYPE); + dataManager.addParameter(ALLOWED_ITEM_TAGS); + dataManager.addParameter(ALLOWED_FLUID_TAGS); } @Override diff --git a/src/main/resources/assets/refinedstorage/lang/en_us.json b/src/main/resources/assets/refinedstorage/lang/en_us.json index 5d9241784..0e9ac957a 100644 --- a/src/main/resources/assets/refinedstorage/lang/en_us.json +++ b/src/main/resources/assets/refinedstorage/lang/en_us.json @@ -84,6 +84,8 @@ "gui.refinedstorage.security_manager.permission.5.tooltip": "Ability to change security options", "gui.refinedstorage.storage_monitor": "Storage Monitor", "gui.refinedstorage.crafter_manager": "Crafter Manager", + "gui.refinedstorage.input_configuration": "Input configuration", + "gui.refinedstorage.input_configuration.apply": "Apply", "misc.refinedstorage.energy_stored": "%d / %d FE", "misc.refinedstorage.energy_usage": "Usage: %d FE/t", "misc.refinedstorage.energy_usage_minimal": "%d FE/t", @@ -103,6 +105,13 @@ "misc.refinedstorage.pattern.outputs": "Outputs", "misc.refinedstorage.pattern.invalid": "Invalid pattern", "misc.refinedstorage.pattern.exact": "Uses exact mode", + "misc.refinedstorage.pattern.allowed_item_tag": "Uses items from %s for %s", + "misc.refinedstorage.pattern.allowed_fluid_tag": "Uses fluids from %s for %s", + "misc.refinedstorage.pattern.error.processing_no_outputs": "Processing pattern has no outputs", + "misc.refinedstorage.pattern.error.no_output": "Recipe has no output", + "misc.refinedstorage.pattern.error.recipe_does_not_exist": "Recipe doesn't exist", + "misc.refinedstorage.pattern.error.tag_no_longer_applicable": "Tag %s is no longer applicable for %s", + "misc.refinedstorage.pattern.error.recipe_no_ingredients": "Recipe has no ingredients", "misc.refinedstorage.security.no_permission": "You have no permission to perform that action.", "misc.refinedstorage.start": "Start", "misc.refinedstorage.clear": "Clear", diff --git a/src/main/resources/assets/refinedstorage/textures/gui/input_configuration.png b/src/main/resources/assets/refinedstorage/textures/gui/input_configuration.png new file mode 100644 index 0000000000000000000000000000000000000000..ca567fdf3843af143c56338122d2397d825a7369 GIT binary patch literal 1675 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&di49pAxJ|V6^adUI?(9qEC?(Sp9j@`a}`~Uy{K*3Qk8UiCB1d5OP@`G|s zNswPKFy)VAz!3bMT@@JToCO|{#S9F5+d!D{+=-n_85o#2JY5_^D&pSW-JPdwDB^PP z;^ANa?d75u^R1b~;!z^2b3w9n`GcDN&oG}P4 zMWeaHym)({`A^;yf=VhB-UFr`y7!W;4xBH#EBAj_9}7_WoSZm30XF=3<-~Ymdpt-V z7(mT{a89h(6KSaVEee&RmR1Mb1CKw?k6e4@yXR)cZ5%+;EvsWdc1(DF9hheD@EYBr4q%wYQPPy5D_^JkrBe|#3kc;XtHLbP;4PAB-H>LcU0SX9DS3j3^P6