-
Notifications
You must be signed in to change notification settings - Fork 756
/
ModifierRecipe.java
225 lines (199 loc) · 8.06 KB
/
ModifierRecipe.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package slimeknights.tconstruct.library.recipe.modifiers.adding;
import com.google.common.collect.ImmutableList;
import com.google.gson.JsonObject;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.Level;
import slimeknights.mantle.recipe.ingredient.SizedIngredient;
import slimeknights.mantle.util.JsonHelper;
import slimeknights.tconstruct.TConstruct;
import slimeknights.tconstruct.library.modifiers.ModifierEntry;
import slimeknights.tconstruct.library.recipe.ITinkerableContainer;
import slimeknights.tconstruct.library.recipe.modifiers.ModifierMatch;
import slimeknights.tconstruct.library.recipe.tinkerstation.IMutableTinkerStationContainer;
import slimeknights.tconstruct.library.recipe.tinkerstation.ITinkerStationContainer;
import slimeknights.tconstruct.library.recipe.tinkerstation.ValidatedResult;
import slimeknights.tconstruct.library.tools.SlotType.SlotCount;
import slimeknights.tconstruct.library.tools.nbt.ModDataNBT;
import slimeknights.tconstruct.library.tools.nbt.ToolStack;
import slimeknights.tconstruct.tools.TinkerModifiers;
import javax.annotation.Nullable;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
/**
* Standard recipe to add a modifier
*/
public class ModifierRecipe extends AbstractModifierRecipe {
/**
* List of input ingredients.
* Order matters, as if a ingredient matches multiple ingredients it may produce unexpected behavior.
* Making the most strict first will produce the best behavior
*/
protected final List<SizedIngredient> inputs;
public ModifierRecipe(ResourceLocation id, List<SizedIngredient> inputs, Ingredient toolRequirement, int maxToolSize, ModifierMatch requirements, String requirementsError, ModifierEntry result, int maxLevel, @Nullable SlotCount slots) {
super(id, toolRequirement, maxToolSize, requirements, requirementsError, result, maxLevel, slots);
this.inputs = inputs;
}
/**
* Creates the bitset used for marking inputs we do not care about
* @param inv Alloy tank
* @return Bitset
*/
protected static BitSet makeBitset(ITinkerableContainer inv) {
int inputs = inv.getInputCount();
BitSet used = new BitSet(inputs);
// mark empty as used to save a bit of effort
for (int i = 0; i < inputs; i++) {
if (inv.getInput(i).isEmpty()) {
used.set(i);
}
}
return used;
}
/**
* Finds a match for the given ingredient
* @param ingredient Ingredient to check
* @param inv Alloy tank to search
* @param used Bitset for already used matches, will be modified
* @return Index of found match, or -1 if match not found
*/
protected static int findMatch(SizedIngredient ingredient, ITinkerableContainer inv, BitSet used) {
ItemStack stack;
for (int i = 0; i < inv.getInputCount(); i++) {
// must not have used that fluid yet
if (!used.get(i)) {
stack = inv.getInput(i);
if (ingredient.test(stack)) {
used.set(i);
return i;
}
}
}
return -1;
}
/**
* Tries to match the given list of ingredients to the inventory
* @param inv Inventory to check
* @param inputs List of inputs to check
* @return True if a match
*/
public static boolean checkMatch(ITinkerableContainer inv, List<SizedIngredient> inputs) {
BitSet used = makeBitset(inv);
for (SizedIngredient ingredient : inputs) {
int index = findMatch(ingredient, inv, used);
if (index == -1) {
return false;
}
}
// ensure there are no unused inputs, makes recipes work together awkwardly
for (int i = 0; i < inv.getInputCount(); i++) {
if (!used.get(i) && !inv.getInput(i).isEmpty()) {
return false;
}
}
// goal of matches is to see if this works for any tool, so ignore current tool NBT
return true;
}
@Override
public boolean matches(ITinkerStationContainer inv, Level world) {
// ensure this modifier can be applied
if (!result.isBound() || !this.toolRequirement.test(inv.getTinkerableStack())) {
return false;
}
return checkMatch(inv, inputs);
}
/**
* Gets the recipe result, or an object containing an error message if the recipe matches but cannot be applied.
* @return Validated result
*/
@Override
public ValidatedResult getValidatedResult(ITinkerStationContainer inv) {
ItemStack tinkerable = inv.getTinkerableStack();
ToolStack tool = ToolStack.from(tinkerable);
// common errors
ValidatedResult commonError = validatePrerequisites(tool);
if (commonError.hasError()) {
return commonError;
}
// consume slots
tool = tool.copy();
ModDataNBT persistentData = tool.getPersistentData();
SlotCount slots = getSlots();
if (slots != null) {
persistentData.addSlots(slots.getType(), -slots.getCount());
}
// add modifier
tool.addModifier(result.getId(), result.getLevel());
// ensure no modifier problems
ValidatedResult toolValidation = tool.validate();
if (toolValidation.hasError()) {
return toolValidation;
}
return ValidatedResult.success(tool.createStack(Math.min(tinkerable.getCount(), shrinkToolSlotBy())));
}
/** Updates all inputs in the given container */
public static void updateInputs(ITinkerableContainer.Mutable inv, List<SizedIngredient> inputs) {
// bit corresponding to items that are already found
BitSet used = makeBitset(inv);
// just shrink each input
for (SizedIngredient ingredient : inputs) {
// care about size, if too small just skip the recipe
int index = findMatch(ingredient, inv, used);
if (index != -1) {
inv.shrinkInput(index, ingredient.getAmountNeeded());
} else {
TConstruct.LOG.warn("Missing ingredient in modifier recipe input consume");
}
}
}
@Override
public void updateInputs(ItemStack result, IMutableTinkerStationContainer inv, boolean isServer) {
updateInputs(inv, inputs);
}
@Override
public RecipeSerializer<?> getSerializer() {
return TinkerModifiers.modifierSerializer.get();
}
/* JEI display */
@Override
public int getInputCount() {
return inputs.size();
}
@Override
public List<ItemStack> getDisplayItems(int slot) {
if (slot >= 0 && slot < inputs.size()) {
return inputs.get(slot).getMatchingStacks();
}
return Collections.emptyList();
}
public static class Serializer extends AbstractModifierRecipe.Serializer<ModifierRecipe> {
@Override
public ModifierRecipe fromJson(ResourceLocation id, JsonObject json, Ingredient toolRequirement, int maxToolSize, ModifierMatch requirements,
String requirementsError, ModifierEntry result, int maxLevel, @Nullable SlotCount slots) {
List<SizedIngredient> ingredients = JsonHelper.parseList(json, "inputs", SizedIngredient::deserialize);
return new ModifierRecipe(id, ingredients, toolRequirement, maxToolSize, requirements, requirementsError, result, maxLevel, slots);
}
@Override
public ModifierRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf buffer, Ingredient toolRequirement, int maxToolSize, ModifierMatch requirements,
String requirementsError, ModifierEntry result, int maxLevel, @Nullable SlotCount slots) {
int size = buffer.readVarInt();
ImmutableList.Builder<SizedIngredient> builder = ImmutableList.builder();
for (int i = 0; i < size; i++) {
builder.add(SizedIngredient.read(buffer));
}
return new ModifierRecipe(id, builder.build(), toolRequirement, maxToolSize, requirements, requirementsError, result, maxLevel, slots);
}
@Override
protected void toNetworkSafe(FriendlyByteBuf buffer, ModifierRecipe recipe) {
super.toNetworkSafe(buffer, recipe);
buffer.writeVarInt(recipe.inputs.size());
for (SizedIngredient ingredient : recipe.inputs) {
ingredient.write(buffer);
}
}
}
}