WIP autocrafting using java recipe book

work in progress. many edge cases are currently unhandled. will not work at all pre 1.12. (support is planned)
This commit is contained in:
AJ Ferguson 2021-01-03 17:54:26 -09:00
parent 528a9a4431
commit 8928d554a1
6 changed files with 130 additions and 30 deletions

View file

@ -61,6 +61,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
@ -230,6 +231,7 @@ public class GeyserSession implements CommandSender {
@Setter
private Int2ObjectMap<Recipe> craftingRecipes;
private final Set<String> unlockedRecipes;
/**
* Saves a list of all stonecutter recipes, for use in a stonecutter inventory.
@ -382,6 +384,7 @@ public class GeyserSession implements CommandSender {
this.openInventory = null;
this.inventoryFuture = CompletableFuture.completedFuture(null);
this.craftingRecipes = new Int2ObjectOpenHashMap<>();
this.unlockedRecipes = new ObjectOpenHashSet<>();
this.spawned = false;
this.loggedIn = false;

View file

@ -27,8 +27,12 @@ package org.geysermc.connector.network.translators.inventory;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientPrepareCraftingGridPacket;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
@ -552,7 +556,8 @@ public abstract class InventoryTranslator {
int recipeId = 0;
int resultSize = 0;
boolean autoCraft;
int timesCrafted = 0;
boolean autoCraft = false;
CraftState craftState = CraftState.START;
int leftover = 0;
@ -566,28 +571,30 @@ public abstract class InventoryTranslator {
}
craftState = CraftState.RECIPE_ID;
recipeId = craftAction.getRecipeNetworkId();
//System.out.println(session.getCraftingRecipes().get(recipeId));
autoCraft = false;
break;
}
// case CRAFT_RECIPE_AUTO: {
// AutoCraftRecipeStackRequestActionData autoCraftAction = (AutoCraftRecipeStackRequestActionData) action;
// if (craftState != CraftState.START) {
// return rejectRequest(request);
// }
// craftState = CraftState.RECIPE_ID;
// recipeId = autoCraftAction.getRecipeNetworkId();
// Recipe recipe = session.getCraftingRecipes().get(recipeId);
// System.out.println(recipe);
// if (recipe == null) {
// return rejectRequest(request);
// }
//// ClientPrepareCraftingGridPacket packet = new ClientPrepareCraftingGridPacket(session.getOpenInventory().getId(), recipe.getIdentifier(), true);
//// session.sendDownstreamPacket(packet);
// autoCraft = true;
// //TODO: reject transaction if crafting grid is not clear
// break;
// }
case CRAFT_RECIPE_AUTO: {
AutoCraftRecipeStackRequestActionData autoCraftAction = (AutoCraftRecipeStackRequestActionData) action;
if (craftState != CraftState.START) {
return rejectRequest(request);
}
craftState = CraftState.RECIPE_ID;
recipeId = autoCraftAction.getRecipeNetworkId();
if (!plan.getCursor().isEmpty()) {
return rejectRequest(request);
}
//reject if crafting grid is not clear
int gridSize = inventory.getId() == 0 ? 4 : 9;
for (int i = 1; i <= gridSize; i++) {
if (!inventory.getItem(i).isEmpty()) {
return rejectRequest(request);
}
}
autoCraft = true;
break;
}
case CRAFT_RESULTS_DEPRECATED: {
CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action;
if (craftState != CraftState.RECIPE_ID) {
@ -599,7 +606,8 @@ public abstract class InventoryTranslator {
return rejectRequest(request);
}
resultSize = deprecatedCraftAction.getResultItems()[0].getCount();
if (resultSize <= 0) {
timesCrafted = deprecatedCraftAction.getTimesCrafted();
if (resultSize <= 0 || timesCrafted <= 0) {
return rejectRequest(request);
}
break;
@ -628,11 +636,45 @@ public abstract class InventoryTranslator {
}
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
int destSlot = bedrockSlotToJava(transferAction.getDestination());
if (autoCraft) {
Recipe recipe = session.getCraftingRecipes().get(recipeId);
//cannot use java recipe book if recipe is locked
if (recipe == null || !session.getUnlockedRecipes().contains(recipe.getIdentifier())) {
return rejectRequest(request);
}
boolean cursorDest = isCursor(transferAction.getDestination());
boolean makeAll = timesCrafted > 1;
if (cursorDest) {
makeAll = false;
}
ClientPrepareCraftingGridPacket prepareCraftingPacket = new ClientPrepareCraftingGridPacket(inventory.getId(), recipe.getIdentifier(), makeAll);
session.sendDownstreamPacket(prepareCraftingPacket);
ItemStack output = null;
switch (recipe.getType()) {
case CRAFTING_SHAPED:
output = ((ShapedRecipeData)recipe.getData()).getResult();
break;
case CRAFTING_SHAPELESS:
output = ((ShapelessRecipeData)recipe.getData()).getResult();
break;
}
inventory.setItem(0, GeyserItemStack.from(output), session);
plan.add(cursorDest ? Click.LEFT : Click.LEFT_SHIFT, 0);
plan.execute(true);
return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet()));
}
if (isCursor(transferAction.getDestination())) {
plan.add(Click.LEFT, sourceSlot);
craftState = CraftState.DONE;
} else {
int destSlot = bedrockSlotToJava(transferAction.getDestination());
if (leftover != 0) {
if (transferAction.getCount() > leftover) {
return rejectRequest(request);

View file

@ -25,16 +25,14 @@
package org.geysermc.connector.network.translators.inventory.click;
import com.github.steveice10.mc.protocol.data.game.window.ClickItemParam;
import com.github.steveice10.mc.protocol.data.game.window.DropItemParam;
import com.github.steveice10.mc.protocol.data.game.window.WindowAction;
import com.github.steveice10.mc.protocol.data.game.window.WindowActionParam;
import com.github.steveice10.mc.protocol.data.game.window.*;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum Click {
LEFT(WindowAction.CLICK_ITEM, ClickItemParam.LEFT_CLICK),
RIGHT(WindowAction.CLICK_ITEM, ClickItemParam.RIGHT_CLICK),
LEFT_SHIFT(WindowAction.SHIFT_CLICK_ITEM, ShiftClickItemParam.LEFT_CLICK),
DROP_ONE(WindowAction.DROP_ITEM, DropItemParam.DROP_FROM_SELECTED),
DROP_ALL(WindowAction.DROP_ITEM, DropItemParam.DROP_SELECTED_STACK),
LEFT_OUTSIDE(WindowAction.CLICK_ITEM, ClickItemParam.LEFT_CLICK),

View file

@ -166,11 +166,16 @@ public class ClickPlan {
GeyserItemStack clicked = simulating ? getItem(action.slot) : inventory.getItem(action.slot);
if (translator.getSlotType(action.slot) == SlotType.OUTPUT) {
switch (action.click) {
case LEFT:
case RIGHT:
if (cursor.isEmpty() && !clicked.isEmpty()) {
setCursor(clicked.copy());
} else if (InventoryUtils.canStack(cursor, clicked)) {
cursor.add(clicked.getAmount());
}
break;
}
} else {
switch (action.click) {
case LEFT:
@ -195,6 +200,9 @@ public class ClickPlan {
clicked.add(1);
}
break;
case LEFT_SHIFT:
//TODO
break;
case DROP_ONE:
if (!clicked.isEmpty()) {
clicked.sub(1);

View file

@ -183,6 +183,7 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator<ServerDeclare
session.sendUpstreamPacket(craftingDataPacket);
session.setCraftingRecipes(recipeMap);
session.getUnlockedRecipes().clear();
session.setStonecutterRecipes(stonecutterRecipeMap);
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.java;
import com.github.steveice10.mc.protocol.data.game.UnlockRecipesAction;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerUnlockRecipesPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import java.util.Arrays;
@Translator(packet = ServerUnlockRecipesPacket.class)
public class JavaUnlockRecipesTranslator extends PacketTranslator<ServerUnlockRecipesPacket> {
@Override
public void translate(ServerUnlockRecipesPacket packet, GeyserSession session) {
if (packet.getAction() == UnlockRecipesAction.REMOVE) {
session.getUnlockedRecipes().removeAll(Arrays.asList(packet.getRecipes()));
} else {
session.getUnlockedRecipes().addAll(Arrays.asList(packet.getRecipes()));
}
}
}