Skip to content

Commit 9a819eb

Browse files
committed
make placing recipe from recipe book working with custom ingredients
1 parent ed272c8 commit 9a819eb

15 files changed

Lines changed: 658 additions & 12 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* This file is part of Sponge, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) SpongePowered <https://www.spongepowered.org>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package org.spongepowered.common.bridge.world.item.crafting;
26+
27+
import net.minecraft.world.entity.player.StackedContents;
28+
import net.minecraft.world.item.ItemStack;
29+
30+
import java.util.List;
31+
32+
public interface PlacementInfoBridge {
33+
34+
List<StackedContents.IngredientInfo<ItemStack>> bridge$stackIngredientInfos();
35+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* This file is part of Sponge, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) SpongePowered <https://www.spongepowered.org>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package org.spongepowered.common.bridge.world.item.crafting;
26+
27+
public interface RecipeBridge {
28+
29+
default boolean bridge$hasCustomIngredients() {
30+
return false;
31+
}
32+
}

src/main/java/org/spongepowered/common/bridge/world/item/crafting/ShapedRecipeBridge.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@
2626

2727
import net.minecraft.world.item.crafting.ShapedRecipePattern;
2828

29-
public interface ShapedRecipeBridge extends RecipeResultBridge {
29+
public interface ShapedRecipeBridge extends RecipeBridge, RecipeResultBridge {
3030
ShapedRecipePattern bridge$pattern();
3131
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* This file is part of Sponge, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) SpongePowered <https://www.spongepowered.org>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package org.spongepowered.common.item.recipe.book;
26+
27+
import net.minecraft.core.Holder;
28+
import net.minecraft.recipebook.ServerPlaceRecipe;
29+
import net.minecraft.world.entity.player.Inventory;
30+
import net.minecraft.world.inventory.Slot;
31+
import net.minecraft.world.item.ItemStack;
32+
33+
public final class RecipeBookUtil {
34+
35+
/**
36+
* Copied from {@link ServerPlaceRecipe#moveItemToGrid(Slot, Holder, int)}
37+
* and adjusted to use exemplary {@link ItemStack} instead of just item type.
38+
*/
39+
public static int moveItemToGrid(
40+
final Inventory inventory, final Slot craftInputSlot, final ItemStack exemplaryStackToMove, final int amount
41+
) {
42+
final ItemStack craftInputStack = craftInputSlot.getItem();
43+
final int itemToMoveIndex = RecipeBookUtil.findSlotMatchingCraftingIngredient(inventory, exemplaryStackToMove, craftInputStack);
44+
if (itemToMoveIndex == -1) {
45+
return -1;
46+
} else {
47+
final ItemStack inventoryStack = inventory.getItem(itemToMoveIndex);
48+
final ItemStack movedStack;
49+
if (amount < inventoryStack.getCount()) {
50+
movedStack = inventory.removeItem(itemToMoveIndex, amount);
51+
} else {
52+
movedStack = inventory.removeItemNoUpdate(itemToMoveIndex);
53+
}
54+
55+
int movedAmount = movedStack.getCount();
56+
if (craftInputStack.isEmpty()) {
57+
craftInputSlot.set(movedStack);
58+
} else {
59+
craftInputStack.grow(movedAmount);
60+
}
61+
62+
return amount - movedAmount;
63+
}
64+
}
65+
66+
/**
67+
* Copied from {@link Inventory#findSlotMatchingCraftingIngredient(Holder, ItemStack)}
68+
* and adjusted to use exemplary {@link ItemStack} instead of just item type.
69+
*/
70+
public static int findSlotMatchingCraftingIngredient(
71+
final Inventory inventory, final ItemStack exemplaryStackToMove, final ItemStack craftInputStack
72+
) {
73+
for (int i = 0; i < inventory.items.size(); i++) {
74+
final ItemStack stack = inventory.items.get(i);
75+
if (!stack.isEmpty()
76+
&& Inventory.isUsableForCrafting(stack)
77+
&& ItemStack.isSameItemSameComponents(exemplaryStackToMove, stack)
78+
&& (craftInputStack.isEmpty() || ItemStack.isSameItemSameComponents(craftInputStack, stack))) {
79+
return i;
80+
}
81+
}
82+
83+
return -1;
84+
}
85+
86+
private RecipeBookUtil() {
87+
}
88+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* This file is part of Sponge, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) SpongePowered <https://www.spongepowered.org>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package org.spongepowered.common.item.recipe.book;
26+
27+
import net.minecraft.core.Holder;
28+
import net.minecraft.world.entity.player.StackedContents;
29+
import net.minecraft.world.item.Item;
30+
import net.minecraft.world.item.ItemStack;
31+
32+
public class SpongeStackedContentsOutputWrapper implements StackedContents.Output<Holder<Item>> {
33+
34+
private final StackedContents.Output<Holder<Item>> originalOutput;
35+
private final StackedContents.Output<ItemStack> wrappedStackOutput;
36+
37+
public SpongeStackedContentsOutputWrapper(
38+
final StackedContents.Output<Holder<Item>> original,
39+
final StackedContents.Output<ItemStack> toWrap
40+
) {
41+
this.originalOutput = original;
42+
this.wrappedStackOutput = stack -> {
43+
originalOutput.accept(stack.getItemHolder());
44+
toWrap.accept(stack);
45+
};
46+
}
47+
48+
/**
49+
* @deprecated Should not be used directly.
50+
*/
51+
@Deprecated
52+
@Override
53+
public void accept(final Holder<Item> holder) {
54+
this.originalOutput.accept(holder);
55+
}
56+
57+
public StackedContents.Output<ItemStack> stackOutput() {
58+
return this.wrappedStackOutput;
59+
}
60+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* This file is part of Sponge, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) SpongePowered <https://www.spongepowered.org>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package org.spongepowered.common.item.recipe.book;
26+
27+
import it.unimi.dsi.fastutil.Hash;
28+
import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
29+
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenCustomHashMap;
30+
import net.minecraft.core.Holder;
31+
import net.minecraft.world.entity.player.StackedContents;
32+
import net.minecraft.world.entity.player.StackedItemContents;
33+
import net.minecraft.world.item.Item;
34+
import net.minecraft.world.item.ItemStack;
35+
import net.minecraft.world.item.crafting.PlacementInfo;
36+
import net.minecraft.world.item.crafting.Recipe;
37+
import org.checkerframework.checker.nullness.qual.Nullable;
38+
import org.spongepowered.common.bridge.world.item.crafting.PlacementInfoBridge;
39+
40+
import java.util.List;
41+
import java.util.function.Function;
42+
43+
public class SpongeStackedItemContents extends StackedItemContents {
44+
45+
private static final Hash.Strategy<ItemStack> STACK_HASH_STRATEGY = new Hash.Strategy<>() {
46+
@Override
47+
public int hashCode(final ItemStack o) {
48+
return ItemStack.hashItemAndComponents(o);
49+
}
50+
51+
@Override
52+
public boolean equals(final @Nullable ItemStack a, final @Nullable ItemStack b) {
53+
return (a == b) || (a != null && b != null && ItemStack.isSameItemSameComponents(a, b));
54+
}
55+
};
56+
57+
private final Object2ReferenceMap<ItemStack, ItemStack> stackInterner = new Object2ReferenceOpenCustomHashMap<>(STACK_HASH_STRATEGY);
58+
private final StackedContents<ItemStack> stackedContents = new StackedContents<>();
59+
60+
@Override
61+
public void accountStack(final ItemStack stack, final int maxStackSize) {
62+
if (!stack.isEmpty()) {
63+
// StackedContents works on Reference2IntMap, so if we meet stack which "same" copy
64+
// has already been accounted we would need to provide the stack that was met first.
65+
final ItemStack stackToAccount = this.stackInterner.computeIfAbsent(stack, Function.identity());
66+
this.stackedContents.account(stackToAccount, Math.min(stack.getCount(), maxStackSize));
67+
}
68+
}
69+
70+
@Override
71+
public boolean canCraft(
72+
final Recipe<?> recipe, final int amount,
73+
final StackedContents.@Nullable Output<Holder<Item>> output
74+
) {
75+
final PlacementInfo placement = recipe.placementInfo();
76+
return !placement.isImpossibleToPlace()
77+
&& this.stackedContents.tryPick(
78+
((PlacementInfoBridge) placement).bridge$stackIngredientInfos(),
79+
amount, this.unwrapStackOutput(output));
80+
}
81+
82+
@Override
83+
public boolean canCraft(
84+
final List<? extends StackedContents.IngredientInfo<Holder<Item>>> ingredients,
85+
final StackedContents.@Nullable Output<Holder<Item>> output
86+
) {
87+
// By default, this method is not called in the context the instance of this class is created.
88+
// If this happens, it's either error in Sponge impl or
89+
// mixin from some mod (which should be inspected instead of silently doing something that impl does not expect).
90+
throw new UnsupportedOperationException("This method should not have been called, please report about it");
91+
}
92+
93+
@Override
94+
public int getBiggestCraftableStack(
95+
final Recipe<?> recipe, final int maxCount,
96+
final StackedContents.@Nullable Output<Holder<Item>> output
97+
) {
98+
return this.stackedContents.tryPickAll(
99+
((PlacementInfoBridge) recipe.placementInfo()).bridge$stackIngredientInfos(),
100+
maxCount, this.unwrapStackOutput(output));
101+
}
102+
103+
@Override
104+
public void clear() {
105+
this.stackInterner.clear();
106+
this.stackedContents.clear();
107+
}
108+
109+
private StackedContents.@Nullable Output<ItemStack> unwrapStackOutput(
110+
final StackedContents.@Nullable Output<Holder<Item>> output
111+
) {
112+
if (output == null) {
113+
return null;
114+
} else if (output instanceof final SpongeStackedContentsOutputWrapper spongeOutput) {
115+
return spongeOutput.stackOutput();
116+
} else {
117+
// By default, this method is only called with wrapped outputs.
118+
// If this happens, there is either error in Sponge impl or
119+
// mixin from some mod and Sponge mixin is not applied last.
120+
throw new UnsupportedOperationException("This should not have happened, please report about it");
121+
}
122+
}
123+
}

src/main/java/org/spongepowered/common/item/recipe/crafting/shapeless/SpongeShapelessRecipe.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import net.minecraft.world.item.crafting.ShapelessRecipe;
3434
import net.minecraft.world.level.Level;
3535
import org.spongepowered.common.accessor.world.item.crafting.ShapelessRecipeAccessor;
36-
import org.spongepowered.common.item.recipe.ingredient.SpongeIngredient;
36+
import org.spongepowered.common.bridge.world.item.crafting.RecipeBridge;
3737

3838
import java.util.ArrayList;
3939
import java.util.Collection;
@@ -50,8 +50,6 @@
5050
*/
5151
public class SpongeShapelessRecipe extends ShapelessRecipe {
5252

53-
private final boolean onlyVanillaIngredients;
54-
5553
private final Function<CraftingInput, ItemStack> resultFunction;
5654
private final Function<CraftingInput, NonNullList<net.minecraft.world.item.ItemStack>> remainingItemsFunction;
5755

@@ -62,14 +60,13 @@ public SpongeShapelessRecipe(final String groupIn,
6260
final Function<CraftingInput, net.minecraft.world.item.ItemStack> resultFunction,
6361
final Function<CraftingInput, NonNullList<ItemStack>> remainingItemsFunction) {
6462
super(groupIn, category, spongeResultStack, recipeItemsIn);
65-
this.onlyVanillaIngredients = recipeItemsIn.stream().noneMatch(i -> i instanceof SpongeIngredient);
6663
this.resultFunction = resultFunction;
6764
this.remainingItemsFunction = remainingItemsFunction;
6865
}
6966

7067
@Override
7168
public boolean matches(final CraftingInput $$0, final Level $$1) {
72-
if (this.onlyVanillaIngredients) {
69+
if (!((RecipeBridge) this).bridge$hasCustomIngredients()) {
7370
return super.matches($$0, $$1);
7471
}
7572
return SpongeShapelessRecipe.matches($$0.items(), ((ShapelessRecipeAccessor) this).accessor$ingredients());

src/main/java/org/spongepowered/common/item/recipe/ingredient/IngredientUtil.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public static org.spongepowered.api.item.recipe.crafting.Ingredient of(ResourceK
7979
return IngredientUtil.fromNative(ingredient);
8080
}
8181

82-
83-
82+
public static boolean isCustom(final @Nullable Ingredient ingredient) {
83+
return ingredient instanceof SpongeIngredient;
84+
}
8485
}

0 commit comments

Comments
 (0)