Skip to content

Commit

Permalink
Quick craft implementation. (CloudburstMC#1473)
Browse files Browse the repository at this point in the history
* Crafting changes with quick craft backport.

* Rename variables.

* Update InventoryTransaction.java
  • Loading branch information
Extollite committed Jun 5, 2020
1 parent 5257262 commit 8a09f93
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 309 deletions.
11 changes: 9 additions & 2 deletions src/main/java/cn/nukkit/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -2990,8 +2990,15 @@ public void onCompletion(Server server) {

return;
} else if (this.craftingTransaction != null) {
this.server.getLogger().debug("Got unexpected normal inventory action with incomplete crafting transaction from " + this.getName() + ", refusing to execute crafting");
this.craftingTransaction = null;
if(craftingTransaction.checkForCraftingPart(actions)){
for (InventoryAction action : actions) {
craftingTransaction.addAction(action);
}
return;
} else {
this.server.getLogger().debug("Got unexpected normal inventory action with incomplete crafting transaction from " + this.getName() + ", refusing to execute crafting");
this.craftingTransaction = null;
}
}

switch (transactionPacket.transactionType) {
Expand Down
8 changes: 1 addition & 7 deletions src/main/java/cn/nukkit/event/inventory/CraftItemEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,8 @@ public static HandlerList getHandlers() {
public CraftItemEvent(CraftingTransaction transaction) {
this.transaction = transaction;

List<Item> merged = new ArrayList<>();
Item[][] input = transaction.getInputMap();

for (Item[] items : input) {
merged.addAll(Arrays.asList(items));
}
this.player = transaction.getSource();
this.input = merged.toArray(new Item[0]);
this.input = transaction.getInputList().toArray(new Item[0]);
this.recipe = transaction.getRecipe();
}

Expand Down
58 changes: 22 additions & 36 deletions src/main/java/cn/nukkit/inventory/CraftingManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -254,28 +254,10 @@ private static int getItemHash(int id, int meta) {
public void registerShapedRecipe(ShapedRecipe recipe) {
int resultHash = getItemHash(recipe.getResult());
Map<UUID, ShapedRecipe> map = shapedRecipes.computeIfAbsent(resultHash, k -> new HashMap<>());
map.put(getMultiItemHash(recipe.getIngredientList()), recipe);
List<Item> inputList = new LinkedList<>(recipe.getIngredientsAggregate());
map.put(getMultiItemHash(inputList), recipe);
}

private Item[][] cloneItemMap(Item[][] map) {
Item[][] newMap = new Item[map.length][];
for (int i = 0; i < newMap.length; i++) {
Item[] old = map[i];
Item[] n = new Item[old.length];

System.arraycopy(old, 0, n, 0, n.length);
newMap[i] = n;
}

for (int y = 0; y < newMap.length; y++) {
Item[] row = newMap[y];
for (int x = 0; x < row.length; x++) {
Item item = newMap[y][x];
newMap[y][x] = item.clone();
}
}
return newMap;
}

public void registerRecipe(Recipe recipe) {
if (recipe instanceof CraftingRecipe) {
Expand All @@ -289,8 +271,7 @@ public void registerRecipe(Recipe recipe) {
}

public void registerShapelessRecipe(ShapelessRecipe recipe) {
List<Item> list = recipe.getIngredientList();
list.sort(recipeComparator);
List<Item> list = recipe.getIngredientsAggregate();

UUID hash = getMultiItemHash(list);

Expand Down Expand Up @@ -335,40 +316,36 @@ public ContainerRecipe matchContainerRecipe(Item input, Item potion) {
return this.containerRecipes.get(getContainerHash(input.getId(), potion.getId()));
}

public CraftingRecipe matchRecipe(Item[][] inputMap, Item primaryOutput, Item[][] extraOutputMap) {
public CraftingRecipe matchRecipe(List<Item> inputList, Item primaryOutput, List<Item> extraOutputList) {
//TODO: try to match special recipes before anything else (first they need to be implemented!)

int outputHash = getItemHash(primaryOutput);
if (this.shapedRecipes.containsKey(outputHash)) {
List<Item> itemCol = new ArrayList<>();
for (Item[] items : inputMap) itemCol.addAll(Arrays.asList(items));
UUID inputHash = getMultiItemHash(itemCol);
inputList.sort(recipeComparator);

UUID inputHash = getMultiItemHash(inputList);

Map<UUID, ShapedRecipe> recipeMap = shapedRecipes.get(outputHash);

if (recipeMap != null) {
ShapedRecipe recipe = recipeMap.get(inputHash);

if (recipe != null && recipe.matchItems(this.cloneItemMap(inputMap), this.cloneItemMap(extraOutputMap))) { //matched a recipe by hash
if (recipe != null && (recipe.matchItems(inputList, extraOutputList) || matchItemsAccumulation(recipe, inputList, primaryOutput, extraOutputList))) {
return recipe;
}

for (ShapedRecipe shapedRecipe : recipeMap.values()) {
if (shapedRecipe.matchItems(this.cloneItemMap(inputMap), this.cloneItemMap(extraOutputMap))) {
if (shapedRecipe.matchItems(inputList, extraOutputList) || matchItemsAccumulation(shapedRecipe, inputList, primaryOutput, extraOutputList)) {
return shapedRecipe;
}
}
}
}

if (shapelessRecipes.containsKey(outputHash)) {
List<Item> list = new ArrayList<>();
for (Item[] a : inputMap) {
list.addAll(Arrays.asList(a));
}
list.sort(recipeComparator);
inputList.sort(recipeComparator);

UUID inputHash = getMultiItemHash(list);
UUID inputHash = getMultiItemHash(inputList);

Map<UUID, ShapelessRecipe> recipes = shapelessRecipes.get(outputHash);

Expand All @@ -378,12 +355,12 @@ public CraftingRecipe matchRecipe(Item[][] inputMap, Item primaryOutput, Item[][

ShapelessRecipe recipe = recipes.get(inputHash);

if (recipe != null && recipe.matchItems(this.cloneItemMap(inputMap), this.cloneItemMap(extraOutputMap))) {
if (recipe != null && (recipe.matchItems(inputList, extraOutputList) || matchItemsAccumulation(recipe, inputList, primaryOutput, extraOutputList))) {
return recipe;
}

for (ShapelessRecipe shapelessRecipe : recipes.values()) {
if (shapelessRecipe.matchItems(this.cloneItemMap(inputMap), this.cloneItemMap(extraOutputMap))) {
if (shapelessRecipe.matchItems(inputList, extraOutputList) || matchItemsAccumulation(shapelessRecipe, inputList, primaryOutput, extraOutputList)) {
return shapelessRecipe;
}
}
Expand All @@ -392,6 +369,15 @@ public CraftingRecipe matchRecipe(Item[][] inputMap, Item primaryOutput, Item[][
return null;
}

private boolean matchItemsAccumulation(CraftingRecipe recipe, List<Item> inputList, Item primaryOutput, List<Item> extraOutputList) {
Item recipeResult = recipe.getResult();
if (primaryOutput.equals(recipeResult, recipeResult.hasMeta(), recipeResult.hasCompoundTag()) && primaryOutput.getCount() % recipeResult.getCount() == 0) {
int multiplier = primaryOutput.getCount() / recipeResult.getCount();
return recipe.matchItems(inputList, extraOutputList, multiplier);
}
return false;
}

public static class Entry {
final int resultItemId;
final int resultMeta;
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/cn/nukkit/inventory/CraftingRecipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ public interface CraftingRecipe extends Recipe {
* Returns whether the specified list of crafting grid inputs and outputs matches this recipe. Outputs DO NOT
* include the primary result item.
*
* @param input 2D array of items taken from the crafting grid
* @param output 2D array of items put back into the crafting grid (secondary results)
* @param inputList list of items taken from the crafting grid
* @param extraOutputList list of items put back into the crafting grid (secondary results)
* @return bool
*/
boolean matchItems(Item[][] input, Item[][] output);
boolean matchItems(List<Item> inputList, List<Item> extraOutputList);

boolean matchItems(List<Item> inputList, List<Item> extraOutputList, int multiplier);

List<Item> getIngredientsAggregate();
}
154 changes: 99 additions & 55 deletions src/main/java/cn/nukkit/inventory/ShapedRecipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
public class ShapedRecipe implements CraftingRecipe {

private String recipeId;
private Item primaryResult;
private List<Item> extraResults = new ArrayList<>();
private final Item primaryResult;
private final List<Item> extraResults = new ArrayList<>();

private final List<Item> ingredientsAggregate;

private long least,most;

Expand Down Expand Up @@ -52,7 +54,7 @@ public ShapedRecipe(String recipeId, int priority, Item primaryResult, String[]
}

int columnCount = shape[0].length();
if (columnCount > 3 || rowCount <= 0) {
if (columnCount > 3 || columnCount <= 0) {
throw new RuntimeException("Shaped recipes may only have 1, 2 or 3 columns, not " + columnCount);
}

Expand Down Expand Up @@ -80,6 +82,23 @@ public ShapedRecipe(String recipeId, int priority, Item primaryResult, String[]
for (Map.Entry<Character, Item> entry : ingredients.entrySet()) {
this.setIngredient(entry.getKey(), entry.getValue());
}

this.ingredientsAggregate = new ArrayList<>();
for (char c : String.join("", this.shape).toCharArray()) {
if (c == ' ')
continue;
Item ingredient = this.ingredients.get(c).clone();
for (Item existingIngredient : this.ingredientsAggregate) {
if (existingIngredient.equals(ingredient, ingredient.hasMeta(), ingredient.hasCompoundTag())) {
existingIngredient.setCount(existingIngredient.getCount() + ingredient.getCount());
ingredient = null;
break;
}
}
if (ingredient != null)
this.ingredientsAggregate.add(ingredient);
}
this.ingredientsAggregate.sort(CraftingManager.recipeComparator);
}

public int getWidth() {
Expand Down Expand Up @@ -180,8 +199,9 @@ public List<Item> getExtraResults() {

@Override
public List<Item> getAllResults() {
List<Item> list = new ArrayList<>(this.extraResults);
List<Item> list = new ArrayList<>();
list.add(primaryResult);
list.addAll(extraResults);

return list;
}
Expand All @@ -191,74 +211,93 @@ public int getPriority() {
return this.priority;
}

@Override
public boolean matchItems(Item[][] input, Item[][] output) {
if (!matchInputMap(Utils.clone2dArray(input))) {

Item[][] reverse = Utils.clone2dArray(input);

for (int y = 0; y < reverse.length; y++) {
reverse[y] = Utils.reverseArray(reverse[y], false);
public boolean matchItems(List<Item> inputList, List<Item> extraOutputList, int multiplier) {
List<Item> haveInputs = new ArrayList<>();
for (Item item : inputList) {
if (item.isNull())
continue;
haveInputs.add(item.clone());
}
List<Item> needInputs = new ArrayList<>();
if(multiplier != 1){
for (Item item : ingredientsAggregate) {
if (item.isNull())
continue;
Item itemClone = item.clone();
itemClone.setCount(itemClone.getCount() * multiplier);
needInputs.add(itemClone);
}

if (!matchInputMap(reverse)) {
return false;
} else {
for (Item item : ingredientsAggregate) {
if (item.isNull())
continue;
needInputs.add(item.clone());
}
}

//and then, finally, check that the output items are good:
List<Item> haveItems = new ArrayList<>();
for (Item[] items : output) {
haveItems.addAll(Arrays.asList(items));
if (!matchItemList(haveInputs, needInputs)) {
return false;
}

List<Item> needItems = this.getExtraResults();

for (Item haveItem : new ArrayList<>(haveItems)) {
if (haveItem.isNull()) {
haveItems.remove(haveItem);
List<Item> haveOutputs = new ArrayList<>();
for (Item item : extraOutputList) {
if (item.isNull())
continue;
haveOutputs.add(item.clone());
}
haveOutputs.sort(CraftingManager.recipeComparator);
List<Item> needOutputs = new ArrayList<>();
if(multiplier != 1){
for (Item item : getExtraResults()) {
if (item.isNull())
continue;
Item itemClone = item.clone();
itemClone.setCount(itemClone.getCount() * multiplier);
needOutputs.add(itemClone);
}

for (Item needItem : new ArrayList<>(needItems)) {
if (needItem.equals(haveItem, needItem.hasMeta(), needItem.hasCompoundTag()) && needItem.getCount() == haveItem.getCount()) {
haveItems.remove(haveItem);
needItems.remove(needItem);
break;
}
} else {
for (Item item : getExtraResults()) {
if (item.isNull())
continue;
needOutputs.add(item.clone());
}
}
needOutputs.sort(CraftingManager.recipeComparator);

return haveItems.isEmpty() && needItems.isEmpty();
return this.matchItemList(haveOutputs, needOutputs);
}

private boolean matchInputMap(Item[][] input) {
Map<Integer, Map<Integer, Item>> map = this.getIngredientMap();

//match the given items to the requested items
for (int y = 0, y2 = this.getHeight(); y < y2; ++y) {
for (int x = 0, x2 = this.getWidth(); x < x2; ++x) {
Item given = input[y][x];
Item required = map.get(y).get(x);

if (given == null || !required.equals(given, required.hasMeta(), required.hasCompoundTag()) || required.getCount() != given.getCount()) {
return false;
}

input[y][x] = null;
}
}
/**
* Returns whether the specified list of crafting grid inputs and outputs matches this recipe. Outputs DO NOT
* include the primary result item.
*
* @param inputList list of items taken from the crafting grid
* @param extraOutputList list of items put back into the crafting grid (secondary results)
* @return bool
*/
@Override
public boolean matchItems(List<Item> inputList, List<Item> extraOutputList) {
return matchItems(inputList, extraOutputList, 1);
}

//check if there are any items left in the grid outside of the recipe
for (Item[] items : input) {
for (Item item : items) {
if (item != null && !item.isNull()) {
return false;
private boolean matchItemList(List<Item> haveItems, List<Item> needItems) {
for (Item needItem : new ArrayList<>(needItems)) {
for (Item haveItem : new ArrayList<>(haveItems)) {
if (needItem.equals(haveItem, needItem.hasMeta(), needItem.hasCompoundTag())) {
int amount = Math.min(haveItem.getCount(), needItem.getCount());
needItem.setCount(needItem.getCount() - amount);
haveItem.setCount(haveItem.getCount() - amount);
if (haveItem.getCount() == 0) {
haveItems.remove(haveItem);
}
if (needItem.getCount() == 0) {
needItems.remove(needItem);
break;
}
}
}
}

return true;
return haveItems.isEmpty() && needItems.isEmpty();
}

@Override
Expand All @@ -274,6 +313,11 @@ public boolean requiresCraftingTable() {
return this.getHeight() > 2 || this.getWidth() > 2;
}

@Override
public List<Item> getIngredientsAggregate() {
return ingredientsAggregate;
}

public static class Entry {
public final int x;
public final int y;
Expand Down

0 comments on commit 8a09f93

Please sign in to comment.