• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyclopsMC / IntegratedDynamics / 24301014474

12 Apr 2026 07:00AM UTC coverage: 53.89% (+0.07%) from 53.819%
24301014474

Pull #1655

github

web-flow
Fix mechanical drying basin to process item and fluid inputs with priority ordering

Agent-Logs-Url: https://github.com/CyclopsMC/IntegratedDynamics/sessions/177cd332-e686-4796-8b4b-b790df5fe999

Co-authored-by: rubensworks <440384+rubensworks@users.noreply.github.com>
Pull Request #1655: [WIP] Fix input selection issue in mechanical drying basin

3086 of 8959 branches covered (34.45%)

Branch coverage included in aggregate %.

18829 of 31707 relevant lines covered (59.38%)

3.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.19
/src/main/java/org/cyclops/integrateddynamics/blockentity/BlockEntityMechanicalDryingBasin.java
1
package org.cyclops.integrateddynamics.blockentity;
2

3
import net.minecraft.core.BlockPos;
4
import net.minecraft.core.Direction;
5
import net.minecraft.core.NonNullList;
6
import net.minecraft.network.chat.Component;
7
import net.minecraft.world.MenuProvider;
8
import net.minecraft.world.entity.player.Inventory;
9
import net.minecraft.world.entity.player.Player;
10
import net.minecraft.world.inventory.AbstractContainerMenu;
11
import net.minecraft.world.item.ItemStack;
12
import net.minecraft.world.item.crafting.RecipeHolder;
13
import net.minecraft.world.item.crafting.RecipeType;
14
import net.minecraft.world.level.block.entity.BlockEntityType;
15
import net.minecraft.world.level.block.state.BlockState;
16
import net.minecraft.world.level.storage.ValueInput;
17
import net.minecraft.world.level.storage.ValueOutput;
18
import net.neoforged.neoforge.fluids.FluidStack;
19
import net.neoforged.neoforge.transfer.fluid.FluidResource;
20
import net.neoforged.neoforge.transfer.transaction.Transaction;
21
import org.apache.commons.lang3.tuple.Pair;
22
import org.cyclops.cyclopscore.datastructure.SingleCache;
23
import org.cyclops.cyclopscore.fluid.SingleUseTank;
24
import org.cyclops.cyclopscore.helper.IModHelpers;
25
import org.cyclops.cyclopscore.helper.IModHelpersNeoForge;
26
import org.cyclops.cyclopscore.recipe.type.IInventoryFluid;
27
import org.cyclops.cyclopscore.recipe.type.InventoryFluid;
28
import org.cyclops.integrateddynamics.RegistryEntries;
29
import org.cyclops.integrateddynamics.block.BlockMechanicalDryingBasin;
30
import org.cyclops.integrateddynamics.block.BlockMechanicalDryingBasinConfig;
31
import org.cyclops.integrateddynamics.core.blockentity.BlockEntityMechanicalMachine;
32
import org.cyclops.integrateddynamics.core.recipe.handler.RecipeHandlerDryingBasin;
33
import org.cyclops.integrateddynamics.core.recipe.type.RecipeMechanicalDryingBasin;
34
import org.cyclops.integrateddynamics.inventory.container.ContainerMechanicalDryingBasin;
35

36
import javax.annotation.Nullable;
37
import java.util.Optional;
38
import java.util.function.Supplier;
39

40
/**
41
 * A part entity for the mechanical drying basin.
42
 * @author rubensworks
43
 */
44
public class BlockEntityMechanicalDryingBasin extends BlockEntityMechanicalMachine<Pair<ItemStack, FluidStack>, RecipeMechanicalDryingBasin>
45
        implements MenuProvider {
46

47
    public static final int INVENTORY_SIZE = 5;
48

49
    private static final int SLOT_INPUT = 0;
50
    private static final int[] SLOTS_OUTPUT = {1, 2, 3, 4};
20✔
51

52
    private final SingleUseTank tankIn = new SingleUseTank(IModHelpersNeoForge.get().getFluidHelpers().getBucketVolume() * 10);
10✔
53
    private final SingleUseTank tankOut = new SingleUseTank(IModHelpersNeoForge.get().getFluidHelpers().getBucketVolume() * 100);
10✔
54

55
    public BlockEntityMechanicalDryingBasin(BlockPos blockPos, BlockState blockState) {
56
        super(RegistryEntries.BLOCK_ENTITY_MECHANICAL_DRYING_BASIN.get(), blockPos, blockState, INVENTORY_SIZE);
8✔
57

58
        // Add tank update listeners
59
        tankIn.addDirtyMarkListener(this::onTankChanged);
5✔
60
        tankOut.addDirtyMarkListener(this::onTankChanged);
5✔
61
    }
1✔
62

63
    public static class CapabilityRegistrar extends BlockEntityMechanicalMachine.CapabilityRegistrar<BlockEntityMechanicalDryingBasin> {
64
        public CapabilityRegistrar(Supplier<BlockEntityType<? extends BlockEntityMechanicalDryingBasin>> blockEntityType) {
65
            super(blockEntityType);
3✔
66
        }
1✔
67

68
        @Override
69
        public void populate() {
70
            super.populate();
2✔
71

72
            add(
4✔
73
                    net.neoforged.neoforge.capabilities.Capabilities.Fluid.BLOCK,
74
                    (blockEntity, direction) -> direction == Direction.DOWN ? blockEntity.getTankOutput() : blockEntity.getTankInput()
×
75
            );
76
            add(
4✔
77
                    org.cyclops.commoncapabilities.api.capability.Capabilities.RecipeHandler.BLOCK,
78
                    (blockEntity, direction) -> new RecipeHandlerDryingBasin<>(blockEntity::getLevel, RegistryEntries.RECIPETYPE_MECHANICAL_DRYING_BASIN.get())
×
79
            );
80
        }
1✔
81
    }
82

83
    @Override
84
    protected SingleCache.ICacheUpdater<Pair<ItemStack, FluidStack>, Optional<RecipeHolder<RecipeMechanicalDryingBasin>>> createCacheUpdater() {
85
        return new SingleCache.ICacheUpdater<Pair<ItemStack, FluidStack>, Optional<RecipeHolder<RecipeMechanicalDryingBasin>>>() {
14✔
86
            @Override
87
            public Optional<RecipeHolder<RecipeMechanicalDryingBasin>> getNewValue(Pair<ItemStack, FluidStack> key) {
88
                // First, try matching with both item and fluid inputs
89
                IInventoryFluid recipeInput = new InventoryFluid(
8✔
90
                        NonNullList.of(ItemStack.EMPTY, key.getLeft()),
10✔
91
                        NonNullList.of(FluidStack.EMPTY, key.getRight()));
6✔
92
                Optional<RecipeHolder<RecipeMechanicalDryingBasin>> recipe = IModHelpers.get().getCraftingHelpers().findRecipe(getRecipeRegistry(), recipeInput, getLevel());
11✔
93
                if (recipe.isPresent()) {
3✔
94
                    return recipe;
2✔
95
                }
96

97
                // If both item and fluid are present but no combined recipe was found,
98
                // try item-only, then fluid-only, to handle the case where the machine has
99
                // two separate types of inputs and should process one at a time.
100
                if (!key.getLeft().isEmpty() && !key.getRight().isEmpty()) {
10!
101
                    recipeInput = new InventoryFluid(
8✔
102
                            NonNullList.of(ItemStack.EMPTY, key.getLeft()),
11✔
103
                            NonNullList.of(FluidStack.EMPTY, FluidStack.EMPTY));
3✔
104
                    recipe = IModHelpers.get().getCraftingHelpers().findRecipe(getRecipeRegistry(), recipeInput, getLevel());
11✔
105
                    if (recipe.isPresent()) {
3!
106
                        return recipe;
2✔
107
                    }
108

109
                    recipeInput = new InventoryFluid(
×
110
                            NonNullList.of(ItemStack.EMPTY, ItemStack.EMPTY),
×
111
                            NonNullList.of(FluidStack.EMPTY, key.getRight()));
×
112
                    return IModHelpers.get().getCraftingHelpers().findRecipe(getRecipeRegistry(), recipeInput, getLevel());
×
113
                }
114

115
                return Optional.empty();
2✔
116
            }
117

118
            @Override
119
            public boolean isKeyEqual(Pair<ItemStack, FluidStack> cacheKey, Pair<ItemStack, FluidStack> newKey) {
120
                return cacheKey == null || newKey == null ||
6!
121
                        (ItemStack.matches(cacheKey.getLeft(), newKey.getLeft()) &&
8✔
122
                                FluidStack.matches(cacheKey.getRight(), newKey.getRight()));
10✔
123
            }
124
        };
125
    }
126

127
    @Override
128
    public int[] getInputSlots() {
129
        return new int[]{SLOT_INPUT};
×
130
    }
131

132
    @Override
133
    public int[] getOutputSlots() {
134
        return SLOTS_OUTPUT;
×
135
    }
136

137
    @Override
138
    public boolean wasWorking() {
139
        return getLevel().getBlockState(getBlockPos()).getValue(BlockMechanicalDryingBasin.LIT);
10✔
140
    }
141

142
    @Override
143
    public void setWorking(boolean working) {
144
        getLevel().setBlockAndUpdate(getBlockPos(), getLevel().getBlockState(getBlockPos())
13✔
145
                .setValue(BlockMechanicalDryingBasin.LIT, working));
3✔
146
    }
1✔
147

148
    public SingleUseTank getTankInput() {
149
        return tankIn;
3✔
150
    }
151

152
    public SingleUseTank getTankOutput() {
153
        return tankOut;
3✔
154
    }
155

156
    @Override
157
    public void read(ValueInput input) {
158
        super.read(input);
×
159
        getTankInput().deserialize(input, "tankIn");
×
160
        getTankOutput().deserialize(input, "tankOut");
×
161
    }
×
162

163
    @Override
164
    public void saveAdditional(ValueOutput output) {
165
        getTankInput().serialize(output, "tankIn");
5✔
166
        getTankOutput().serialize(output, "tankOut");
5✔
167
        super.saveAdditional(output);
3✔
168
    }
1✔
169

170
    @Override
171
    protected RecipeType<RecipeMechanicalDryingBasin> getRecipeRegistry() {
172
        return RegistryEntries.RECIPETYPE_MECHANICAL_DRYING_BASIN.get();
4✔
173
    }
174

175
    @Override
176
    protected Pair<ItemStack, FluidStack> getCurrentRecipeCacheKey() {
177
        return Pair.of(getInventory().getItem(SLOT_INPUT).copy(), IModHelpersNeoForge.get().getFluidHelpers().copy(getTankInput().getFluid()));
13✔
178
    }
179

180
    @Override
181
    public int getRecipeDuration(RecipeHolder<RecipeMechanicalDryingBasin> recipe) {
182
        return recipe.value().getDuration();
5✔
183
    }
184

185
    @Override
186
    protected boolean finalizeRecipe(RecipeMechanicalDryingBasin recipe, boolean simulate) {
187
        // Output items
188
        ItemStack outputStack = recipe.getOutputItemFirst().orElse(ItemStack.EMPTY).copy();
7✔
189
        if (!outputStack.isEmpty()) {
3!
190
            if (!IModHelpers.get().getInventoryHelpers().addToInventory(getInventory(), SLOTS_OUTPUT, NonNullList.withSize(1, outputStack), simulate).isEmpty()) {
12!
191
                return false;
×
192
            }
193
        }
194

195
        // Output fluid
196
        Optional<FluidStack> outputFluid = recipe.getOutputFluid();
3✔
197
        if (outputFluid.isPresent()) {
3!
198
            try (var tx = Transaction.openRoot()) {
×
199
                int inserted = getTankOutput().insert(FluidResource.of(outputFluid.get()), outputFluid.get().getAmount(), tx);
×
200
                if (!simulate) {
×
201
                    tx.commit();
×
202
                }
203
                if (inserted != outputFluid.get().getAmount()) {
×
204
                    return false;
×
205
                }
206
            }
×
207
        }
208

209
        // Only consume items if we are not simulating
210
        if (!simulate) {
2✔
211
            if (!recipe.getInputIngredient().isEmpty()) {
4✔
212
                getInventory().removeItem(SLOT_INPUT, 1);
6✔
213
            }
214
        }
215

216
        // Consume fluid
217
        Optional<FluidStack> inputFluid = recipe.getInputFluid();
3✔
218
        if (inputFluid.isPresent()) {
3✔
219
            try (var tx = Transaction.openRoot()) {
2✔
220
                int extracted = getTankInput().extract(FluidResource.of(inputFluid.get()), inputFluid.get().getAmount(), tx);
13✔
221
                if (!simulate) {
2✔
222
                    tx.commit();
2✔
223
                }
224
                if (extracted != inputFluid.get().getAmount()) {
6!
225
                    return false;
×
226
                }
227
            }
×
228
        }
229

230
        return true;
2✔
231
    }
232

233
    @Override
234
    public int getEnergyConsumptionRate() {
235
        return BlockMechanicalDryingBasinConfig.consumptionRate;
2✔
236
    }
237

238
    @Override
239
    public int getMaxEnergyStored() {
240
        return BlockMechanicalDryingBasinConfig.capacity;
2✔
241
    }
242

243
    @Nullable
244
    @Override
245
    public AbstractContainerMenu createMenu(int id, Inventory playerInventory, Player playerEntity) {
246
        return new ContainerMechanicalDryingBasin(id, playerInventory, this.getInventory(), Optional.of(this));
×
247
    }
248

249
    @Override
250
    public Component getDisplayName() {
251
        return Component.translatable("block.integrateddynamics.mechanical_drying_basin");
×
252
    }
253
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc