Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quick craft implementation. #1473

Merged
merged 3 commits into from
Jun 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading